/// <summary>Populates ID property of provided <paramref name="principal"/>.</summary> /// <returns>Whether a new principal is inserted in database.</returns> private bool InsertPrincipalOrGetExisting(ref PrincipalInfo principal) { principal.ID = GetPrincipalID(principal.Name); // Retry reading existing principal after custom lock, maybe another concurrent command created the principal from AuthorizationDataLoader. if (principal.ID != default) { return(false); } try { var newPrincipal = _principalGenericRepository.CreateInstance(); newPrincipal.ID = Guid.NewGuid(); newPrincipal.Name = principal.Name; _principalGenericRepository.Insert(newPrincipal); principal.ID = newPrincipal.ID; return(true); } catch (Exception ex) when(DuplicateNameInDatabase(ex)) { _logger.Warning(() => $"Ignoring concurrent principal creation: {ex.GetType().Name}: {ex.Message}"); principal.ID = GetPrincipalID(principal.Name); // Check if another concurrent command created the principal, but not from AuthorizationDataLoader (the custom lock was not used). if (principal.ID != default) { return(false); } else { throw new FrameworkException($"Cannot create the principal record for '{principal.Name}'.", ex); } } }
/// <summary>Insert a principal with provided <see cref="PrincipalInfo.Name"/> to database, and populates ID property of <paramref name="principal"/> parameter.</summary> /// <remarks>If another concurrent process created a principal, this method will set the ID of the existing one.</remarks> public void SafeInsertPrincipal(ref PrincipalInfo principal) { string username = principal.Name; // Copy to be used for asynchronous logging. _logger.Info(() => $"Adding unregistered principal '{username}'. See {OptionsAttribute.GetConfigurationPath<RhetosAppOptions>()}:{nameof(RhetosAppOptions.AuthorizationAddUnregisteredPrincipals)} in configuration files."); var userLock = CreteCustomLock(username.ToUpper()); bool newCreated = InsertPrincipalOrGetExisting(ref principal); if (!newCreated) // If new principal is created, other requests should wait for current transaction to be committed in order to read the new principal. { ReleaseCustomLock(userLock); } }
public IList <bool> GetAuthorizations(IUserInfo userInfo, IList <Claim> requiredClaims) { PrincipalInfo principal = userInfo.IsUserRecognized ? _authorizationData.GetPrincipal(userInfo.UserName) : null; IEnumerable <Guid> userRoles = GetUsersRoles(principal); IEnumerable <ClaimInfo> claims = GetClaims(requiredClaims); IEnumerable <Permission> userPermissions = GetUsersPermissions(claims, principal, userRoles); IList <bool> userHasClaims = GetUserHasClaims(claims, userPermissions); var roleNamesIndex = new Lazy <IDictionary <Guid, string> >(() => _authorizationData.GetRoles(userRoles)); _logger.Trace(() => ReportRoles(userInfo, userRoles, roleNamesIndex)); _logger.Trace(() => ReportPermissions(userInfo, principal, roleNamesIndex, userPermissions, claims, userHasClaims)); return(userHasClaims); }
public PrincipalInfo GetPrincipal(string username) { var principal = new PrincipalInfo { ID = GetPrincipalID(username), Name = username }; if (principal.ID == default(Guid)) { if (ShouldAddUnregisteredPrincipal()) { _logger.Info(() => "Adding unregistered principal '" + username + "'. See AuthorizationAddUnregisteredPrincipals in web.config."); var newPrincipal = _principalGenericRepository.Value.CreateInstance(); newPrincipal.ID = Guid.NewGuid(); newPrincipal.Name = username; try { _principalGenericRepository.Value.Insert(newPrincipal); principal.ID = newPrincipal.ID; } catch (System.Data.SqlClient.SqlException ex) { if (ex.Message.StartsWith("Cannot insert duplicate key row in object 'Common.Principal' with unique index 'IX_Principal_Name'")) { _logger.Info(() => "Ignoring concurrent principal creation: " + ex.GetType().Name + ": " + ex.Message); principal.ID = GetPrincipalID(username); if (principal.ID == default(Guid)) { throw new FrameworkException("Cannot create the principal record for '" + username + "'.", ex); } } else { throw; } } } else { _logger.Info($"There is no principal with the username '{username}' in Common.Principal."); throw new UserException($"Your account '{username}' is not registered in the system. Please contact the system administrator."); } } return(principal); }
public PrincipalInfo GetPrincipal(string username) { var principal = new PrincipalInfo { ID = GetPrincipalID(username), Name = username }; if (principal.ID != default) { return(principal); } else if (_rhetosAppOptions.AuthorizationAddUnregisteredPrincipals) { _principalWriter.Value.SafeInsertPrincipal(ref principal); return(principal); } else { _logger.Warning($"There is no principal with the username '{username}' in Common.Principal."); throw new UserException($"Your account '{username}' is not registered in the system. Please contact the system administrator."); } }
public PrincipalInfo GetPrincipal(string username) { var principal = new PrincipalInfo { ID = GetPrincipalID(username), Name = username }; if (principal.ID == default(Guid)) { if (ShouldAddUnregisteredPrincipal()) { _logger.Info(() => "Adding unregistered principal '" + username + "'. See AuthorizationAddUnregisteredPrincipals in web.config."); var newPrincipal = _principalGenericRepository.Value.CreateInstance(); newPrincipal.ID = Guid.NewGuid(); newPrincipal.Name = username; try { _principalGenericRepository.Value.Insert(newPrincipal); principal.ID = newPrincipal.ID; } catch (System.Data.SqlClient.SqlException ex) { if (ex.Message.StartsWith("Cannot insert duplicate key row in object 'Common.Principal' with unique index 'IX_Principal_Name'")) { _logger.Info(() => "Ignoring concurrent principal creation: " + ex.GetType().Name + ": " + ex.Message); principal.ID = GetPrincipalID(username); if (principal.ID == default(Guid)) throw new FrameworkException("Cannot create the principal record for '" + username + "'.", ex); } else throw; } } else { _logger.Error("There is no principal with the given username '" + username + "' in Common.Principal."); throw new ClientException("There is no principal with the given username."); } } return principal; }
/// <summary> /// Reporting is done in a function that returns a string, to avoid any performance impact when the trace log is not enabled. /// </summary> private string ReportPermissions(IUserInfo userInfo, PrincipalInfo principal, Lazy<IDictionary<Guid, string>> roleNamesIndex, IEnumerable<Permission> userPermissions, IEnumerable<ClaimInfo> claims, IEnumerable<bool> userHasClaims) { var report = new List<string>(); // Create an index of permissions: var permissionsByClaim = new MultiDictionary<Guid, Permission>(); foreach (var permission in userPermissions) permissionsByClaim.Add(permission.ClaimID, permission); // Analyze permissions for required claims: foreach (var claimResult in claims.Zip(userHasClaims, (Claim, UserHasIt) => new { Claim, UserHasIt })) { var claimPermissions = claimResult.Claim.ID != null ? permissionsByClaim.Get(claimResult.Claim.ID.Value) : new Permission[] { }; var permissionsDescription = claimPermissions .Select(permission => new { permission.IsAuthorized, PrincipalOrRoleName = permission.PrincipalID != null ? ("principal " + (permission.PrincipalID.Value == principal.ID ? principal.Name : permission.PrincipalID.Value.ToString())) : ("role " + GetRoleNameSafe(permission.RoleID.Value, roleNamesIndex)) }) .ToList(); var allowedFor = permissionsDescription.Where(p => p.IsAuthorized).Select(p => p.PrincipalOrRoleName).ToList(); var deniedFor = permissionsDescription.Where(p => !p.IsAuthorized).Select(p => p.PrincipalOrRoleName).ToList(); string explanation = "User " + userInfo.UserName + " " + (claimResult.UserHasIt ? "has" : "doesn't have") + " claim '" + claimResult.Claim.Resource + " " + claimResult.Claim.Right + "'. It is "; if (deniedFor.Count != 0) if (allowedFor.Count != 0) explanation += "denied for " + string.Join(", ", deniedFor) + " and allowed for " + string.Join(", ", allowedFor) + " (deny overrides allow)."; else explanation += "denied for " + string.Join(", ", deniedFor) + "."; else if (allowedFor.Count != 0) explanation += "allowed for " + string.Join(", ", allowedFor) + "."; else if (claimResult.Claim.ID == null) explanation += "denied by default (the claim does not exist or is no longer active)."; else explanation += "denied by default (no permissions defined)."; report.Add(explanation); } return string.Join("\r\n", report); }
/// <summary>Returns principal, ID instead of the principal name, if the name is not available.</summary> private static string GetPermissionsPrincipalNameSafe(Permission permission, PrincipalInfo principal) { return(principal != null && permission.PrincipalID == principal.ID ? principal.Name : permission.PrincipalID.Value.ToString()); }
/// <summary> /// Reporting is done in a function that returns a string, to avoid any performance impact when the trace log is not enabled. /// </summary> private string ReportPermissions(IUserInfo userInfo, PrincipalInfo principal, Lazy <IDictionary <Guid, string> > roleNamesIndex, IEnumerable <Permission> userPermissions, IEnumerable <ClaimInfo> claims, IEnumerable <bool> userHasClaims) { var report = new List <string>(); // Create an index of permissions: var permissionsByClaim = new MultiDictionary <Guid, Permission>(); foreach (var permission in userPermissions) { permissionsByClaim.Add(permission.ClaimID, permission); } // Analyze permissions for required claims: foreach (var claimResult in claims.Zip(userHasClaims, (Claim, UserHasIt) => new { Claim, UserHasIt })) { var claimPermissions = claimResult.Claim.ID != null ? permissionsByClaim.Get(claimResult.Claim.ID.Value) : Array.Empty <Permission>(); var permissionsDescription = claimPermissions .Select(permission => new { permission.IsAuthorized, PrincipalOrRoleName = permission.PrincipalID != null ? ("principal " + GetPermissionsPrincipalNameSafe(permission, principal)) : ("role " + GetRoleNameSafe(permission.RoleID.Value, roleNamesIndex)) }) .ToList(); var allowedFor = permissionsDescription.Where(p => p.IsAuthorized).Select(p => p.PrincipalOrRoleName).ToList(); var deniedFor = permissionsDescription.Where(p => !p.IsAuthorized).Select(p => p.PrincipalOrRoleName).ToList(); string explanation; if (deniedFor.Count != 0) { if (allowedFor.Count != 0) { explanation = $"It is denied for {string.Join(", ", deniedFor)} and allowed for {string.Join(", ", allowedFor)} (deny overrides allow)."; } else { explanation = $"It is denied for {string.Join(", ", deniedFor)}."; } } else if (allowedFor.Count != 0) { explanation = $"It is allowed for {string.Join(", ", allowedFor)}."; } else if (claimResult.Claim.ID == null) { explanation = "It is denied by default (the claim does not exist or is no longer active)."; } else { explanation = "It is denied by default (no permissions defined)."; } report.Add($"User {ReportUserNameOrAnonymous(userInfo)} {(claimResult.UserHasIt ? "has" : "doesn't have")} claim '{claimResult.Claim.Resource} {claimResult.Claim.Right}'. {explanation}"); } return(string.Join("\r\n", report)); }
/// <summary> /// Reporting is done in a function that returns a string, to avoid any performance impact when the trace log is not enabled. /// </summary> private string ReportPermissions(IUserInfo userInfo, PrincipalInfo principal, Lazy <IDictionary <Guid, string> > roleNamesIndex, IEnumerable <Permission> userPermissions, IEnumerable <ClaimInfo> claims, IEnumerable <bool> userHasClaims) { var report = new List <string>(); // Create an index of permissions: var permissionsByClaim = new MultiDictionary <Guid, Permission>(); foreach (var permission in userPermissions) { permissionsByClaim.Add(permission.ClaimID, permission); } // Analyze permissions for required claims: foreach (var claimResult in claims.Zip(userHasClaims, (Claim, UserHasIt) => new { Claim, UserHasIt })) { var claimPermissions = claimResult.Claim.ID != null ? permissionsByClaim.Get(claimResult.Claim.ID.Value) : new Permission[] { }; var permissionsDescription = claimPermissions .Select(permission => new { permission.IsAuthorized, PrincipalOrRoleName = permission.PrincipalID != null ? ("principal " + (permission.PrincipalID.Value == principal.ID ? principal.Name : permission.PrincipalID.Value.ToString())) : ("role " + GetRoleNameSafe(permission.RoleID.Value, roleNamesIndex)) }) .ToList(); var allowedFor = permissionsDescription.Where(p => p.IsAuthorized).Select(p => p.PrincipalOrRoleName).ToList(); var deniedFor = permissionsDescription.Where(p => !p.IsAuthorized).Select(p => p.PrincipalOrRoleName).ToList(); string explanation = "User " + userInfo.UserName + " " + (claimResult.UserHasIt ? "has" : "doesn't have") + " claim '" + claimResult.Claim.Resource + " " + claimResult.Claim.Right + "'. It is "; if (deniedFor.Count != 0) { if (allowedFor.Count != 0) { explanation += "denied for " + string.Join(", ", deniedFor) + " and allowed for " + string.Join(", ", allowedFor) + " (deny overrides allow)."; } else { explanation += "denied for " + string.Join(", ", deniedFor) + "."; } } else if (allowedFor.Count != 0) { explanation += "allowed for " + string.Join(", ", allowedFor) + "."; } else if (claimResult.Claim.ID == null) { explanation += "denied by default (the claim does not exist or is no longer active)."; } else { explanation += "denied by default (no permissions defined)."; } report.Add(explanation); } return(string.Join("\r\n", report)); }