Login History without Manage Users permissions
Recently someone asked me how to expose the login history without giving the individual users the manage users permission. The goal was to allow the internal support team to view the login history of portal users. Off the top of my head, I thought this would be the perfect use case for illustrating the power of the ‘without sharing’ Apex keywords. So after a few minutes we had built an Apex controller and Visualforce page we thought would work. Initial testing, as System Administrator users was promising. Loading the Visualforce page showed us the last 25 login history records for the User we specified. Unfortunately, loading that same page as a support user yielded 25 blank lines!
All was not lost, however, as we refactored the controller more in line with best practices. Moving our login history data to a wrapper class allowed the data to be visible to users who had permission to access the controller and page! Here’s the final product:
And here’s the code:
public without sharing class UserLoginHistory{ | |
public class loginHistoryWrapper { | |
public DateTime LoginTime {get;set;} | |
public String Status {get;set;} | |
public String LoginURL {get;set;} | |
public String LoginType {get;set;} | |
public String Application {get;set;} | |
public String Browser {get;set;} | |
public ID UserId {get;set;} | |
public loginHistoryWrapper(DateTime lt, String statuss, String LR, String ltype, String app, String brow, ID uid){ | |
LoginTime = lt; | |
Status = statuss; | |
LoginURL = LR; | |
LoginType = ltype; | |
Application = app; | |
Browser = brow; | |
UserId = uid; | |
} | |
} | |
private Map<String, String> UrlParameterMap; | |
private User pU {get; set;} | |
public User u { | |
get{ | |
if(pU == null){ | |
pU = [SELECT name FROM User WHERE ID = :UrlParameterMap.get('userId')]; | |
} | |
return pU; | |
} | |
} | |
public List<LoginHistoryWrapper> Records {get; set;} | |
public UserLoginHistory(){ | |
UrlParameterMap = ApexPages.currentPage().getParameters(); | |
List<LoginHistory> lRecords = [ | |
SELECT LoginTime, Status, LoginURL, LoginType, Application, Browser, UserId | |
FROM LoginHistory | |
WHERE UserId= :UrlParameterMap.get('userId') | |
ORDER BY LoginTime DESC LIMIT 25]; | |
Records = new List<LoginHistoryWrapper>(); | |
For(LoginHistory lh : lRecords){ | |
Records.add(new LoginHistoryWrapper(lh.loginTime, lh.status, lh.loginURL, lh.LoginType, lh.Application, lh.Browser, lh.UserId)); | |
} | |
} | |
} |
The secret sauce here is our wrapper object. Converting the loginHistory objects to our wrapper object allows the data to be seen by users without the Manage Users Permission.
<apex:page controller="UserLoginHistory"> | |
<apex:pageBlock title="Login History for {!u.Name}"> | |
<apex:pageBlockTable value="{!Records}" var="Record"> | |
<apex:column > | |
<apex:facet name="header">User's Name</apex:facet> | |
<apex:outputText value=" {!u.name}"/> | |
</apex:column> | |
<apex:column > | |
<apex:facet name="header">Login Time</apex:facet> | |
<apex:outputText value=" {!Record.LoginTime}"/> | |
</apex:column> | |
<apex:column > | |
<apex:facet name="header">Status</apex:facet> | |
<apex:outputText value="{!Record.Status}"/> | |
</apex:column> | |
<apex:column > | |
<apex:facet name="header">Login Type</apex:facet> | |
<apex:outputText value="{!Record.LoginType}"/> | |
</apex:column> | |
<apex:column > | |
<apex:facet name="header">Client Type</apex:facet> | |
<apex:outputText value="{!Record.Application}"/> | |
</apex:column> | |
<apex:column > | |
<apex:facet name="header">Browser</apex:facet> | |
<apex:outputText value="{!Record.Browser}"/> | |
</apex:column> | |
<apex:column > | |
<apex:facet name="header">Login URL</apex:facet> | |
<apex:outputText value="{!Record.LoginURL}"/> | |
</apex:column> | |
</apex:pageBlockTable> | |
</apex:pageBlock> | |
</apex:page> |
Viola! a safe way to expose login history with internal users without giving them ManageUsers permission!