I have a winforms (VB 2008) based app that I'm developing and I want to use custom roles for user access.
Application Layout:
I have a Main form that opens a login form when certain actions occur. The Login form intern uses an authentication class that I've created, to authenticate users and set access rights. On my Applications settings page, I have Authentication Mode set to Application-defined because I cannot use Windows Authentication in the environment where this will be deployed.
The application uses a MS SQL 2005 db and the 3 tables I'm using in the authentication process are the User_Account , User_Roles and User_Access tables. The combination of an account in the User_Account and the roles within the User_Roles table are the bases for the User_Access table. Using the User_Access table is how I assign access to the various functions within the application
Authentication Method:
To authenticate a user, I'm using the "My.User.CurrentPrincipal" (Code below) method. The My.User object works great and allows the use of "My.User.Name" property throughout the app when referring to the currently authenticated user.
Access Method:
In order to set the current users access levels I'm using a function within my Authentication class and passing in My.User.Name as a variable. The function uses a Dataset Table Adaptor and a Select Case statement inside a For loop to assign all the access levels for the user (Function code below).
My Problem:
This method of assigning access rights to a user does work but it's not persistent throughout the application as the My.User object is. I would like to find a way to create custom roles through the My.User object using its .IsInRole property. I would like to have these roles dynamically created using my User_Roles table.
This would allow the custom roles to be used throughout my application using the My.User.IsInRole("MyRole") syntax ...similar to how I'm currently able to use My.User.Name. Unfortunately the only roles I can currently validate against are the built in Windows type accounts (Adminisrator ...ect.).
I have found lots of information and examples related to ASP.Net as well as setting up Winforms Windows authentication but nothing so far directly related to my issue.
I think there's a way to accomplish this...but I have not been able to find it. Any help would be greatly appreciated!!
Thank you for your help!
'User Authentication example:
If Authenticate.CheckPassword(tbxUserName.Text, strPassword) Then
My.User.CurrentPrincipal = New GenericPrincipal(New GenericIdentity(tbxUserName.Text), Nothing)
'Access assignment example:
Public Shared Function GetUser(ByVal strUsername As String) As Authenticate
Using UserAdapter As New dbUserTableAdapters.User_AccountsTableAdapter()
Dim UserTable As dbUser.User_AccountsDataTable = UserAdapter.GetByUser(strUsername)
Dim tempUser As New Authenticate() _
With {.ID = UserTable(0).id, _
.Username = UserTable(0).User_Name, _
.Password = UserTable(0).id}
Using AccessAdapter As New dbUserTableAdapters.User_AccessTableAdapter()
Dim AccessTable As dbUser.User_AccessDataTable = AccessAdapter.GetByUser(tempUser.ID)
For c As Integer = 0 To AccessTable.Rows.Count - 1
Select Case AccessTable(c).Role_Id
Case RoleType.SysAdmin
tempUser.AllowSysAdmin = True
Case RoleType.Maintenance
tempUser.AllowMaintenance = True
Case RoleType.ReportAll
tempUser.AllowRptAll = True
Case RoleType.ReportException
tempUser.AllowRptExceptions = True
Case RoleType.EventManagment
tempUser.AllowEventStart = True
Case Else
End Select
Next
Return tempUser
End Using
End Using
End Function
I think you need to implement a custom IPrincipal object which accesses your SQL table. Try this page.
Edit:
First, have a look at the definitions of IIdentity and IPrincipal. You'll note that IIdentity doesn't have a 'Role' property defined. They've chosen to implement an additional property called Role on their implementation of IIdentity (SampleIIdentity) and then they've used it from their implementation of IPrincipal. What I'm suggesting is that you implement your own Role property (which queries your existing table) and returns one (or an array) of a Role type you define yourself. Then in your implementation of IPrincipal, you can code IsInRole to query the new Role property. Hopefully that makes more sense that my rather skimpy answer.
Related
I customized a new registration page in Blazor Wasm and want to create a new User with new Tenant. I wanted to use ITenantAppService.CreateAsync but it occurred permission problem.
var tenantDto = await _tenantAppService.CreateAsync(new TenantCreateDto()
{
Name = dto.UserName,
AdminEmailAddress = dto.EmailAddress,
AdminPassword = dto.Password,
});
Isn't it possible to create a Tenant by anonymous user?
I think I need to remove this permissions from tenantService or I need to give these permissions to anonymous user.
How can I create new tenant for new user?
I found solution.
Firstly, I tried to remove permissions but I didn't know how to remove easily. Then I tried firstly creating a user without tenant, then give him tenant.create permission but it didn't work.
Finally, I created a class
CustomTenantAppService : TenantManagementAppServiceBase, ITenantAppService
and implemented methods of ITenantAppService, so I could do everything that I want.
My goal is to allow users to connect to our Azure Sql Server using their Azure Active Directory credentials. I'm trying to follow the steps in this article, but I'm getting an error I can't sort out:
Connect to Azure SQL Database with Azure Multi-Factor Authentication
Below are the appropriate pieces of my code, which I largely copied from the example in the article (except my app is written in VB.NET so I had to translate). It requires the Microsoft.IdentityModel.Clients.ActiveDirectory assembly, which I got from NuGet.
Public Module DB
Private ConnectionProvider As ActiveDirectoryAuthProvider
'Gets run at application start
Public Sub SetProvider()
ConnectionProvider = New ActiveDirectoryAuthProvider
SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, ConnectionProvider)
End Sub
End Module
'I can't believe Microsoft doesn't just have this as a class that's already been written
Public Class ActiveDirectoryAuthProvider
Inherits SqlAuthenticationProvider
Private ReadOnly _clientId As String = "Our Client ID From The Azure Portal"
Private ReadOnly _redirectUri As New Uri("A Valid URL")
Public Overrides Async Function AcquireTokenAsync(parameters As SqlAuthenticationParameters) As Task(Of SqlAuthenticationToken)
Dim authContext As New AuthenticationContext(parameters.Authority)
authContext.CorrelationId = parameters.ConnectionId
Dim result As AuthenticationResult
Select Case parameters.AuthenticationMethod
Case SqlAuthenticationMethod.ActiveDirectoryInteractive
result = Await authContext.AcquireTokenAsync(parameters.Resource, _clientId, _redirectUri, New PlatformParameters(PromptBehavior.Auto), New UserIdentifier(parameters.UserId, UserIdentifierType.RequiredDisplayableId))
Case SqlAuthenticationMethod.ActiveDirectoryIntegrated
result = Await authContext.AcquireTokenAsync(parameters.Resource, _clientId, New UserCredential())
Case SqlAuthenticationMethod.ActiveDirectoryPassword
result = Await authContext.AcquireTokenAsync(parameters.Resource, _clientId, New UserPasswordCredential(parameters.UserId, parameters.Password))
Case Else
Throw New InvalidOperationException()
End Select
Return New SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)
End Function
Public Overrides Function IsSupported(ByVal authenticationMethod As SqlAuthenticationMethod) As Boolean
Return authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryIntegrated OrElse authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryInteractive OrElse authenticationMethod = SqlAuthenticationMethod.ActiveDirectoryPassword
End Function
End Class
'And finally, I create new connections like this:
New SqlConnection($"Server=tcp:ourserver.database.windows.net,1433;Initial Catalog=OurDatabase;TrustServerCertificate=True;Pooling=False;Encrypt=True;Authentication=""Active Directory Interactive"";User ID={Environment.UserName}#OurDomain.com;")
Using this code, I do get the popup from Azure asking me to sign in when I run SqlConnection.Open. As soon as I've signed in however, I get the following exception:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException
AADSTS7000218: The request body must contain the following parameter:
'client_assertion' or 'client_secret'.
Any idea how I can fix that?
So, in digging through every resource I could find, I came across this question:
“error_description”:"AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'
The answer linked above points to the "Redirect URI" registered with the application in Azure as the cause of the issue.
The Microsoft article in my question states: "For this article, any valid value is fine for RedirectUri, because it isn't used here." The example they use is: "https://mywebserver.com/".
Contrary to the quote from Microsoft, the answer I linked above points out that Azure uses the Redirect URI to determine the type of application that is being registered. Changing the URI from my company's website to "https://login.microsoftonline.com/common/oauth2/nativeclient" fixes my problem. That URL is one of the default values Azure lets you pick from. It evidently indicates to Azure that you are registering a native app, not a web app. Once Azure knows this, it seems to stop demanding a "'client_assertion' or 'client_secret'", which I can only assume are things required for web app authentication.
I faced with this problem a bit earlier, spent a lot of time to figure out the problem and the only solution which helped was adding of "https://login.microsoftonline.com/common/oauth2/nativeclient" on "Authentication" tab to "Redirect URIs" like it was mentioned by Keith Stein
We use Apache Shiro to authenticate and authorize users using our active directory.
Authenticating the user and mapping groups works just fine using the following config:
adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.searchBase = "OU=MYORGANIZATION,DC=MYDOMAIN,DC=COM"
adRealm.groupRolesMap = "CN=SOMEREADGROUP":"read","CN=SOMEMODIFYGROUP":"modify","CN=SOMEADMINGROUP":"admin"
adRealm.url = ldaps://my.ad.url:636
adRealm.systemUsername= systemuser
adRealm.systemPassword= secret
adRealm.principalSuffix= #myorganization.mydomain.com
I can authenticate in Shiro using the following lines:
String user = "someuser";
String password = "somepassword";
Subject currentUser = SecurityUtils.getSubject ();
if (!currentUser.isAuthenticated ()){
UsernamePasswordToken token = new UsernamePasswordToken (user,
password);
token.setRememberMe (true);
currentUser.login (token);
}
We now want to get more user information from our ActiveDirectory. How can I do that using Apache Shiro? I was not able to find anything about it in the documentation.
In the source code of ActiveDirectoryRealm I found this line:
NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, searchCtls);
So the first part of the answer is clear: use the ldapContext to search something in it. But how can I retrieve the LdapContext?
It depends on what you are trying to do. Are you just trying to reuse the context to run a query for something other then authentication or authorization? Or are you trying to change the behavior of the query in the AD realm?
If the latter, you would need to extend the ActiveDirectoryRealm and override the queryForAuthorizationInfo() method.
Are you implementing something that is custom for your environment?
(updated)
A couple things:
The realm has access to the LdapContext in the two touch points: queryForAuthenticationInfo() and queryForAuthorizationInfo(), so if you extend the AD realm or AbstractLdapRealm you should already have it. You could change the query to return other info and add the extra info to your Principal. Then you have access to that info directly from your Subject object.
Your realms, are not required to be singletons.
If you want to do some other sort of user management (email all users with a given role, create a user, etc). Then you could create a LdapContextFactory in your shiro.ini, and use the same instance for multiple objects.
[main]
...
ldapContextFactory = org.apache.shiro.realm.ldap.JndiLdapContextFactory
ldapContextFactory.systemUsername = foobar
ldapContextFactory.systemPassword = barfoo
adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.ldapContextFactory = $ldapContextFactory
...
myObject = com.biz.myco.MyObject
myObject.ldapContextFactory = $ldapContextFactory
This would work well if myObject is interacting with other Shiro components, (responding to events, etc), but less so if you need access to it from another framework. You could work around this by some sort of static initialization that builds creates the ldapContextFactory, but in my opinion, this is where the sweet spot of using the shiro.ini ends, and where using Guice or Spring shines.
I have a VB.Net Application that authenticates using the current Windows User without asking for the password. It checks that this user is a member of "MYDOMAIN\ApplicationUsers" before it starts up.
How to check if this is the real domain and not a different one using the same name? Are there any certs or public keys to validate locally? I'd prefer to check this offline, without a third party machine or database etc.
In the System.DirectoryServices.ActiveDirectory Namespace are some Trust an Validate methods but they only seem to check inter domain trust and using a domain name only.
Your problem is that you are using strings and strings like mydomain/application users are not unique across domains. One possibility is to use the SID of the application users group in your expected domain instead of the name. Then you can check the SID of the group to make sure it matches the sid for the expected application users group at run time before checking membership. It would be much harder for a malicious user to spoof domain and group parts of the Sid then the domain and group name.
Ultimately if you are running code on a machine that is owned by the malicious user then this just raises the bar and they could still circumvent this check.
I made some example code which checks the group's SID as Mike suggested. You just need to put your group's SID in the constructor of the SecurityIdentifier class to make the check work against the currently logged on user.
Private Sub DoCheck()
Dim sid As New Security.Principal.SecurityIdentifier("S-0-0-00-0000000000-0000000000-0000000000-000"),
result As Boolean
result = IsUserInGroup(sid)
End Sub
Public Shared Function IsUserInGroup(sid As Security.Principal.SecurityIdentifier) As Boolean
Dim user As UserPrincipal
user = UserPrincipal.Current
For Each group As Principal In user.GetGroups()
If group.Sid.Equals(sid) Then Return True
Next
Return False
End Function
To make the code work you need to import System.DirectoryServices.AccountManagement:
Imports System.DirectoryServices.AccountManagement
This namespace is located in Microsoft's System.DirectoryServices.AccountManagement.dll which is available since .Net 4.0 I believe.
In the Thinktecture.IdentityModel.45 library, I can get a Microsoft.IdentityModel.Claims.ClaimsIdentityCollection by executing something like this:
Dim handler = New JsonWebTokenHandler()
handler.Configuration = config ' set elsewhere
Dim identities = handler.ValidateToken(handler.ReadToken(token))
We have a system where a user gets to login and then choose an organizational context they are part of. Each context should be representative of what is available in the token (one identity per organization with a collection of specific claims). How can I get the Thinktecture.IdentityServer.45 to return a token that contains multiple identities?
WIF is generally not designed for this. And only certain token types support this at all.