Logins on my sites are very infrequent and are normally done by myself. I would like a way of getting email notification when someone logs in. This way I might be able to spot someone hacking into the site early on.

I am using Drupal 7.

  • 1
    Do you know the hook hook_user_login ? Or maybe you can use the Rules module, not sure if it's possible that way. – guilhom Aug 1 '17 at 17:50
up vote 5 down vote accepted

A possible solution is to use the Rules module and create a custom rule like so:

  • Rules Event = After a user logs in.
  • Rules Action = Send an eMail.

That's really it (a kind of Rules 101 also if you want ...).

Here is a rule (in Rules import format) that should answer your question:

{ "rules_notify_about_login" : {
    "LABEL" : "Notify admin about login",
    "PLUGIN" : "reaction rule",
    "ACTIVE" : false,
    "OWNER" : "rules",
    "REQUIRES" : [ "rules" ],
    "ON" : { "user_login" : [] },
    "IF" : [
      { "NOT user_has_role" : { "account" : [ "account" ], "roles" : { "value" : { "3" : "3" } } } }
    ],
    "DO" : [
      { "mail" : {
          "to" : "[site:mail]",
          "subject" : "New login",
          "message" : "Some user just completed a login to [site:name], located at [site:url].",
          "language" : [ "" ]
        }
      }
    ]
  }
}

The rule will be triggered (Rules Event) right after login was completed. For any user who does not have the role that matches "role id = 3" (= Rules Condition), aka for any user who is not an administrator, it will send an appropriate eMail to the admin user of the site.

If you have the Rules UI enabled, you can just import the above rule. As an alternative just use the Rules UI to create a similar rule from scratch.

Some further refinements you may want to apply to the above sample rule:

  • Adapt the value (=3) of the role id to fit your needs or just omit the Rules Condition if you want to perform the Rules Action for any role.

  • Include relevant info about the user who performed a login (using available tokens about that user).

  • Adapt the eMail subject and content to fit your needs.

PS: This answer applies to D7, though for D8 it should be similar.

  • 2
    This would be the fastest way IMO. It should work in 7 or 8. – Kevin Aug 1 '17 at 18:27

Sending eMails to be informed about such logins is perfectly possible, as explained in my prior answer. However, eMails may get lost, or may not ever arrive at its destination. So therefor, as an alternative (or supplement?), you might also want to create a "custom log" using the Message module, as further detailed below.

1. Create a Message Type

Use the Message module and create a custom Message Type for creating custom log records whenever a user performs a login. Here is a sample of such message type (in export format, which you should be able to import in your own site also):

{
  "name" : "users_performing_login",
  "description" : "Users performing login",
  "argument_keys" : [ "@login_user_name", "@login_user_id" ],
  "argument" : [],
  "category" : "message_type",
  "data" : {
    "token options" : { "clear" : 0 },
    "purge" : { "override" : 0, "enabled" : 0, "quota" : "", "days" : "" }
  },
  "language" : "",
  "arguments" : null,
  "message_text" : { "und" : [
      {
        "value" : "\u003Cp\u003EUser with name =\u0026nbsp;@login_user_name and uid =\u0026nbsp;@login_user_id just performed a login.\u003C\/p\u003E\r\n",
        "format" : "filtered_html",
        "safe_value" : "\u003Cp\u003EUser with name =\u00a0@login_user_name and uid =\u00a0@login_user_id just performed a login.\u003C\/p\u003E\n"
      }
    ]
  },
  "rdf_mapping" : []
}

Some details about this Message Type:

  • Description:

    Users performing login

  • Message Text:

    User with name = @login_user_name and uid = @login_user_id just performed a login.

  • Replacement tokens (*):

    @login_user_name, @login_user_id

(*): The usage of these "Replacement tokens" is crucial here. For way more details about this (amazing) feature, refer to What's the purpose of "Replacement tokens" of a Message type?.

2. Create a rule for creating messages

Create a rule with a Rules Action "Create entity" (of type "Message", and with Message Type the one created in the previous step). Here is a sample of such rule you could use (in Rules export format, which you should be able to import in your own site also if you have the Rules UI enabled):

{ "rules_logging_about_logins" : {
    "LABEL" : "Custom logging about logins",
    "PLUGIN" : "reaction rule",
    "OWNER" : "rules",
    "REQUIRES" : [ "rules" ],
    "ON" : { "user_login" : [] },
    "IF" : [
      { "NOT user_has_role" : { "account" : [ "account" ], "roles" : { "value" : { "3" : "3" } } } }
    ],
    "DO" : [
      { "entity_create" : {
          "USING" : {
            "type" : "message",
            "param_type" : "users_performing_login",
            "param_user" : [ "account" ]
          },
          "PROVIDE" : { "entity_created" : { "log_record_created" : "Created log record" } }
        }
      },
      { "data_set" : {
          "data" : [ "log-record-created:arguments:login-user-name" ],
          "value" : [ "account:name" ]
        }
      },
      { "data_set" : {
          "data" : [ "log-record-created:arguments:login-user-id" ],
          "value" : [ "account:uid" ]
        }
      }
    ]
  }
}

The Rules Event and Rules Conditions are similar to the rule in my prior answer. While the Rules Actions will take care of creating an appropriate Message. Also note the last 2 Rules Actions: these are to assign the appropriate values to the "Replacement tokens" used in the Message type.

3. Create a view for browsing messages

The rule in the previous step will create a message (with a Message Type "Users performing login"). What is left is to create an appropriate view to browse these messages that get created.

Here is a (rather basic) view to "Show all existing messages." (in Views export format, which you should be able to import in your own site also if you have the Views UI enabled):

$view = new view();
$view->name = 'message';
$view->description = 'Show all existing messages.';
$view->tag = 'Message';
$view->base_table = 'message';
$view->human_name = 'Message';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */

/* Display: Defaults */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'perm';
$handler->display->display_options['access']['perm'] = 'administer message types';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['query']['options']['query_comment'] = FALSE;
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'full';
$handler->display->display_options['pager']['options']['items_per_page'] = '30';
$handler->display->display_options['pager']['options']['offset'] = '0';
$handler->display->display_options['pager']['options']['id'] = '0';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
  'timestamp' => 'timestamp',
  'message_render' => 'message_render',
  'name' => 'name',
);
$handler->display->display_options['style_options']['default'] = '-1';
$handler->display->display_options['style_options']['info'] = array(
  'timestamp' => array(
    'sortable' => 1,
    'default_sort_order' => 'asc',
    'align' => '',
    'separator' => '',
  ),
  'message_render' => array(
    'align' => '',
    'separator' => '',
  ),
  'name' => array(
    'sortable' => 1,
    'default_sort_order' => 'asc',
    'align' => '',
    'separator' => '',
  ),
);
$handler->display->display_options['style_options']['sticky'] = TRUE;
/* No results behavior: Global: Text area */
$handler->display->display_options['empty']['area']['id'] = 'area';
$handler->display->display_options['empty']['area']['table'] = 'views';
$handler->display->display_options['empty']['area']['field'] = 'area';
$handler->display->display_options['empty']['area']['content'] = 'No messages created yet.';
$handler->display->display_options['empty']['area']['format'] = 'filtered_html';
/* Relationship: Message: User uid */
$handler->display->display_options['relationships']['user']['id'] = 'user';
$handler->display->display_options['relationships']['user']['table'] = 'message';
$handler->display->display_options['relationships']['user']['field'] = 'user';
/* Field: Message: Timestamp */
$handler->display->display_options['fields']['timestamp']['id'] = 'timestamp';
$handler->display->display_options['fields']['timestamp']['table'] = 'message';
$handler->display->display_options['fields']['timestamp']['field'] = 'timestamp';
$handler->display->display_options['fields']['timestamp']['date_format'] = 'short';
/* Field: Message: Render message (Get text) */
$handler->display->display_options['fields']['message_render']['id'] = 'message_render';
$handler->display->display_options['fields']['message_render']['table'] = 'message';
$handler->display->display_options['fields']['message_render']['field'] = 'message_render';
$handler->display->display_options['fields']['message_render']['label'] = 'Text';
$handler->display->display_options['fields']['message_render']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['message_render']['hide_alter_empty'] = FALSE;
/* Field: User: Name */
$handler->display->display_options['fields']['name']['id'] = 'name';
$handler->display->display_options['fields']['name']['table'] = 'users';
$handler->display->display_options['fields']['name']['field'] = 'name';
$handler->display->display_options['fields']['name']['relationship'] = 'user';
$handler->display->display_options['fields']['name']['label'] = 'User';
/* Sort criterion: Message: Timestamp */
$handler->display->display_options['sorts']['timestamp']['id'] = 'timestamp';
$handler->display->display_options['sorts']['timestamp']['table'] = 'message';
$handler->display->display_options['sorts']['timestamp']['field'] = 'timestamp';
$handler->display->display_options['sorts']['timestamp']['order'] = 'DESC';
/* Filter criterion: Message: Type */
$handler->display->display_options['filters']['type']['id'] = 'type';
$handler->display->display_options['filters']['type']['table'] = 'message';
$handler->display->display_options['filters']['type']['field'] = 'type';
$handler->display->display_options['filters']['type']['exposed'] = TRUE;
$handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
$handler->display->display_options['filters']['type']['expose']['label'] = 'Message type';
$handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op';
$handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';

/* Display: Page */
$handler = $view->new_display('page', 'Page', 'page_1');
$handler->display->display_options['path'] = 'admin/content/message';
$handler->display->display_options['menu']['type'] = 'tab';
$handler->display->display_options['menu']['title'] = 'Messages';
$handler->display->display_options['menu']['description'] = 'A list of all messages.';
$handler->display->display_options['menu']['weight'] = '0';
$handler->display->display_options['menu']['name'] = 'management';

After you enabled the view, you should navigate to /admin/content/message in your site, where you should see some views result similar to this one (assuming some users performed a login after you enabled the view):

Timestamp           Text    
08/03/2017 - 11:21  User with name = Pierre.Vriens and uid = 3 just performed a login.
08/03/2017 - 10:03  User with name = Dries.Buytaert and uid = 4 just performed a login.
08/02/2017 - 19:17  User with name = Hillary.Trump and uid = 911 just performed a login.
08/01/2017 - 11:55  User with name = Donald.Clinton and uid = 7 just performed a login.

So with this Views result, you have a permanent (custom) log, without the risk of eMails getting lost, or eMails that never arrive. Especially at times when you want to investigate possible hacking, this is IMO a much more solid solution as compared to just "sending an eMail" ... Which IT auditors must love also.

4. Bonus features

a) Tuning the layout of the custom log records

The Message Text in the above scenario is rather basic (apart from some fixed text, it only includes the user's name and user's id). However, the real magic is in the "Replacement tokens": virtually any token that you have available in the custom rule, can be passed along to the Message text to be created. Combined with the zillion of tokens already made available via the Message module, it implies that you can make your Message Text to be whatever you want.

b) Receiving eMail notifications

By using the Message module, you may also combine it with using the Message Notify module. Some details about it (from its project page):

... provides a method for sending a message via a notifier plugin. Message Notify comes with plugins for email and SMS and may be extended to other transport mechanisms as required.

Message Notify is part of the message stack, a suite of modules that provide a framework for messaging and notifications.

5. More info about the Message module

There is not a lot of documentation available about the Message module, which is what makes it a hidden Drupal gem. A possible way to get started with it, and to get an idea of the kind of things it can be used for, is to also have a look at these tutorials:

PS: This answer applies to D7.

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.