Using the SecurityData service in LightSwitch
In this blog post, I’m going to describe how LightSwitch developers can programmatically access the security data contained in a LightSwitch application. Having access to this data is useful for any number of reasons. For example, you can use this API to programmatically add new users to the application.
This data is exposed in code as entities, just like with your own custom data. So it’s an easy and familiar API to work with. And it’s available from both client or server code.
First I’d like to describe the security data model. Here’s a UML diagram describing the service, entities, and supporting classes:
SecurityData
This is the data service class that provides access to the security entities as well as a few security-related methods. It is available from your DataWorkspace object via its SecurityData property.
SecurityData is a LightSwitch data service and behaves in the same way as the data service that gets generated when you add a data source to LightSwitch. It exposes the same type of query and save methods. It just operates on the built-in security entities instead of your entities.
Some important notes regarding having access to your application’s security data:
In a running LightSwitch application, users which do not have the SecurityAdministration permission are only allowed to read security data; they cannot insert, update, or delete it. In addition, those users are only able to read security data that is relevant to themselves. So if a user named Bob, who does not have the SecurityAdministration permission, queries the UserRegistrations entity set, he will only see a UserRegistration with the name of Bob. He will not be able to see that there also exists a UserRegistration with the name of Kim since he is not logged in as Kim. Similarly with roles, if Bob queries the Roles entity set, he can see that a role named SalesPerson exists because he is assigned to that role. But he cannot see that a role named Manager exists because he is not assigned to that role.Users which do have the SecurityAdministration permission are not restricted in their access to security data. They are able to read all stored security data and have the ability to modify it.
In addition to entity sets, SecurityData also exposes a few useful methods:
- ChangePassword
This method allows a user to change their own password. They need to supply their old password in order to do so. If the oldPassword parameter doesn’t match the current password or the new password doesn’t conform to the password requirements, an exception will be thrown. This method is only relevant when Forms authentication is being used. This method can be called by any user; they do not require the SecurityAdministration permission. - GetWindowsUserInfo
This method validates and resolve a Windows account name into a normalized value and retrieve the full name (display name) of that user. As an example, let’s say that you passed “kim@contoso.com” as the parameter to this operation, it would return back a WindowsUserInfo object with the FullName property set to “Kim Abercrombie” and the NormalizedUserName property set to “contoso\kim”. If the service is unable to resolve the username parameter to a known account, an exception will be thrown. This method is only relevant when Windows authentication is being used. This method can only be called by users that have the SecurityAdministration permission. - IsValidPassword
This method checks whether the supplied password meets the password requirements of the application. This happens automatically when attempting to add a new user or updating their password but this method allows the caller to preemptively check the password which can be useful for validation UI purposes. This method is only relevant when Forms authentication is being used. This method can only be called by users that have the SecurityAdministration permission.
UserRegistration
The UserRegistration entity represents a user that has access to the application. (In VS 11, it can also represent an Active Directory security group when Windows authentication is being used). When using Forms authentication, all the UserRegistration properties (UserName, FullName, Password) are required. When using Windows authentication, only the UserName property is required; FullName is populated dynamically based on the UserName and Password is irrelevant.
Here is some example code using the UserRegistration entity:
C#:
// Create a new UserRegistration (Windows authentication)
UserRegistration user = this.DataWorkspace.SecurityData.UserRegistrations.AddNew();
user.UserName = "contoso\\kim";
this.DataWorkspace.SecurityData.SaveChanges();
// Create a new UserRegistration (Forms authentication)
UserRegistration user = this.DataWorkspace.SecurityData.UserRegistrations.AddNew();
user.UserName = "kim_abercrombie";
user.FullName = "Kim Abercrombie";
user.Password = "mysecretpassword!";
this.DataWorkspace.SecurityData.SaveChanges();
// Iterate through all UserRegistrations
foreach (UserRegistration user in this.DataWorkspace.SecurityData.UserRegistrations)
{
}
// Find a specific UserRegistration (Windows authentication)
UserRegistration user = this.DataWorkspace.SecurityData.UserRegistrations_Single("contoso\\kim");
// Find a specific UserRegistration (Forms authentication)
UserRegistration user = this.DataWorkspace.SecurityData.UserRegistrations_Single("kim_abercrombie");
VB:
' Create a new UserRegistration (Windows authentication)
Dim user As UserRegistration = Me.DataWorkspace.SecurityData.UserRegistrations.AddNew()
user.UserName = "contoso\\kim"
Me.DataWorkspace.SecurityData.SaveChanges()
' Create a new UserRegistration (Forms authentication)
Dim user As UserRegistration = Me.DataWorkspace.SecurityData.UserRegistrations.AddNew()
user.UserName = "kim_abercrombie"
user.FullName = "Kim Abercrombie"
user.Password = "mysecretpassword!"
Me.DataWorkspace.SecurityData.SaveChanges()
' Iterate through all UserRegistrations
For Each user As UserRegistration In Me.DataWorkspace.SecurityData.UserRegistrations
Next
' Find a specific UserRegistration (Windows authentication)
Dim user As UserRegistration = Me.DataWorkspace.SecurityData.UserRegistrations_Single("contoso\\kim")
' Find a specific UserRegistration (Forms authentication)
Dim user As UserRegistration = Me.DataWorkspace.SecurityData.UserRegistrations_Single("kim_abercrombie")
Role
The Role entity represents a grouping of users with common characteristics. Examples of roles in an application include “Sales Person” and “Manager”. You can then configure your application security around these roles.
Here is some example code using the Role entity:
C#:
// Create a new role
Role role = this.DataWorkspace.SecurityData.Roles.AddNew();
role.Name = "Manager";
this.DataWorkspace.SecurityData.SaveChanges();
// Iterate through all Roles
foreach (Role role in this.DataWorkspace.SecurityData.Roles)
{
}
// Find a specific role
Role role = this.DataWorkspace.SecurityData.Roles_Single("Manager");
VB:
' Create a new role
Dim role As Role = Me.DataWorkspace.SecurityData.Roles.AddNew()
role.Name = "Manager"
Me.DataWorkspace.SecurityData.SaveChanges()
' Iterate through all Roles
For Each role As Role In Me.DataWorkspace.SecurityData.Roles
Next
' Find a specific role
Dim role As Role = Me.DataWorkspace.SecurityData.Roles_Single("Manager")
RoleAssignment
The RoleAssignment entity represents a mapping between a Role and a UserRegistration. It is how you define that a user belongs to a role.
Here is some example code using the RoleAssignment entity:
C#
// Assign Kim to the Manager role
UserRegistration user = this.DataWorkspace.SecurityData.UserRegistrations_Single("contoso\\kim");
Role role = this.DataWorkspace.SecurityData.Roles_Single("Manager");
RoleAssignment roleAssignment = user.RoleAssignments.AddNew();
roleAssignment.Role = role;
this.DataWorkspace.SecurityData.SaveChanges();
// Find which roles Kim is assigned to
UserRegistration user = this.DataWorkspace.SecurityData.UserRegistrations_Single("contoso\\kim");
foreach (RoleAssignment roleAssignment in user.RoleAssignments)
{
Role role = roleAssignment.Role;
}
VB:
' Assign Kim to the Manager role
Dim user As UserRegistration = Me.DataWorkspace.SecurityData.UserRegistrations_Single("contoso\\kim")
Dim role As Role = Me.DataWorkspace.SecurityData.Roles_Single("Manager")
Dim roleAssignment As RoleAssignment = user.RoleAssignments.AddNew()
roleAssignment.Role = role
Me.DataWorkspace.SecurityData.SaveChanges()
' Find which roles Kim is assigned to
Dim user As UserRegistration = Me.DataWorkspace.SecurityData.UserRegistrations_Single("contoso\\kim")
For Each roleAssignment As RoleAssignment In user.RoleAssignments
Dim role As Role = roleAssignment.Role
Next
Permission
The Permission entity represents an action that a logged in user is permitted to do within the application. Examples of permissions in an application include “CanRejectSalesOrder” and “CanUpdateInventory”. Permissions are different than the rest of the security data because they are read-only. The permissions that are available map to the permissions that were defined within the Access Control tab of the application properties of a LightSwitch project:
Here is some example code using the Permission entity:
C#:
// Iterate through all Permissions
foreach (Permission permission in this.DataWorkspace.SecurityData.Permissions)
{
}
// Find a specific Permission
Permission permission = this.DataWorkspace.SecurityData.Permissions_Single(Permissions.CanRejectSalesOrder);
VB:
' Iterate through all Permissions
For Each permission As Permission In Me.DataWorkspace.SecurityData.Permissions
Next
' Find a specific Permission
Dim permission As Permission = Me.DataWorkspace.SecurityData.Permissions_Single(Permissions.CanRejectSalesOrder)
RolePermission
The RolePermission entity represents a mapping between a Role and a Permission. It is how you define that a permission is assigned to a role.
Here is some example code using the RolePermission entity:
C#:
// Assign CanRejectSalesOrder permission to Manager role
Role role = this.DataWorkspace.SecurityData.Roles_Single("Manager");
Permission permission = this.DataWorkspace.SecurityData.Permissions_Single(Permissions.CanRejectSalesOrder);
RolePermission rolePermission = role.RolePermissions.AddNew();
rolePermission.Permission = permission;
this.DataWorkspace.SecurityData.SaveChanges();
// Find which permissions are assigned to the Manager role
Role role = this.DataWorkspace.SecurityData.Roles_Single("Manager");
foreach (RolePermission rolePermission in role.RolePermissions)
{
Permission permission = rolePermission.Permission;
}
VB:
' Assign CanRejectSalesOrder permission to Manager role
Dim role As Role = Me.DataWorkspace.SecurityData.Roles_Single("Manager")
Dim permission As Permission = Me.DataWorkspace.SecurityData.Permissions_Single(Permissions.CanRejectSalesOrder)
Dim rolePermission As RolePermission = role.RolePermissions.AddNew()
rolePermission.Permission = permission
Me.DataWorkspace.SecurityData.SaveChanges()
' Find which permissions are assigned to the Manager role
Dim role As Role = Me.DataWorkspace.SecurityData.Roles_Single("Manager")
For Each rolePermission As RolePermission In role.RolePermissions
Dim permission As Permission = rolePermission.Permission
Next
Conclusion
Using the SecurityData service and the entities it exposes, you can accomplish all kinds of interesting scenarios that require dynamic management of your application’s security data. I hope you’ve found this blog post helpful in your work. Happy coding.
Comments
Anonymous
March 15, 2012
Nice post, thanks, I know I keep getting asked about functions to allow the users to change their passwords, this will help.Anonymous
March 15, 2012
Thank you!! This answers a question that was posted by a reader on my blog. This is a tremendously valuable article matt. Thanks again, Paul PaulSPatterson.comAnonymous
March 15, 2012
Doh! I forgot to mention the article I was referencing... Using LightSwitch User Information in Lookup Lists... www.paulspatterson.com/.../microsoft-lightswitch-using-user-information-for-lookup-lists Cheers again!Anonymous
March 15, 2012
Thanks for this!Anonymous
March 15, 2012
Thanks! Very helpful!Anonymous
March 15, 2012
Excellent article. Still wondering why there is no option to provide read-only access for non-admin users to the user registrations. This is often needed in a business app, e.g. an app where users assign tasks to each other or where a dropdown of users needs to be available based on a the role of these users. kind regards -paul.Anonymous
March 19, 2012
Thanks for the feedback Paul. We're aware of the demand for a feature that provides read-only access for non-admins. Just need to prioritize it with everything else.Anonymous
March 24, 2012
Hi Matt, I am assuming the example you posted last year on accessing roles and users through WCF RIA service would still then be the recommended approach for accessing lists of users, roles , and users in roles - until a more direct feature of some kind is provided?Anonymous
March 24, 2012
Or per Paul Patterson's articel above as wellAnonymous
March 24, 2012
Another thought- would the new Odata functionality in the new beta allow for accessing the users , roles, and users in roles information in a similar way to the WCF RIA approach?Anonymous
March 26, 2012
@Lyndon: Attaching to the SecurityData service would be an alternate way of consuming it instead of the custom WCF RIA service approach. But I'm not sure it would be better. For one, you would need to specify at attach-time what authentication method you want to use when connecting to the service. If Forms auth is being used, you are essentially hardcoding which account you want to connect to the service with. If Windows auth, you'd be connecting to the SecurityData service with whichever Windows account your connecting service is running as. Maybe this is what you're looking for though. Plus there are performance considerations to take into account. Eric Erhard provides a good response to the general OData vs WCF RIA service question at social.msdn.microsoft.com/.../d10a7b22-2500-438f-8694-8515e9a95c5e.