public static UserBE BuildUserFromAuthService(ServiceBE serviceInfo, UserBE knownUser, string usernameToBuild, bool bypassAuthentication, string authusername, string password, out List <GroupBE> externalGroups) { externalGroups = null; if (serviceInfo == null || string.IsNullOrEmpty(usernameToBuild)) { return(null); } //Dont perform external lookup for disabled users if (knownUser != null && !knownUser.UserActive) { return(knownUser); } var errMsg = DekiResources.UNABLE_TO_AUTH_WITH_SERVICE(serviceInfo.Type, serviceInfo.SID, serviceInfo.Uri); if (knownUser != null && !string.IsNullOrEmpty(knownUser.ExternalName)) { usernameToBuild = knownUser.ExternalName; } UserBE ret = null; DreamMessage response = null; if (serviceInfo.Uri == null) { throw new ExternalServiceNotStartedFatalException(serviceInfo.Type, serviceInfo.SID); } try { Plug dekiExternalAuthPlug; //bypassAuthentication is used when you only need user details but not to necessarily authenticate if (bypassAuthentication) { //An external auth service's GET: user/{username} does not necessarily require authentication to lookup users but it may. It's up to the service //to decide if anon requests are allowed. dekiExternalAuthPlug = Plug.New(serviceInfo.Uri).At(USER_INFO).At(XUri.Encode(usernameToBuild)); } else { //Credentials are always needed for GET: authenticate. The user details of the auth'd user is returned with same format as GET: user/{username} dekiExternalAuthPlug = Plug.New(serviceInfo.Uri).At(AUTHENTICATE_PATH); } //Always include credentials with the request if they're supplied if (!string.IsNullOrEmpty(authusername)) { dekiExternalAuthPlug = dekiExternalAuthPlug.WithCredentials(authusername, password ?? string.Empty); } response = dekiExternalAuthPlug.GetAsync().Wait(); } catch (Exception x) { throw new ExternalServiceResponseException(errMsg, DreamMessage.InternalError(x)); } if (response.IsSuccessful) { XDoc userXml = response.ToDocument(); if (userXml == null || userXml.IsEmpty) { throw new ExternalAuthResponseFatalException(); } string nameFromAuthProvider = userXml["@name"].Contents; if (!nameFromAuthProvider.EqualsInvariantIgnoreCase(usernameToBuild)) { throw new ExternalServiceUnexpecteUsernameFatalException(userXml["@name"].AsText, usernameToBuild); } ret = knownUser ?? new UserBE(); ret.Email = string.IsNullOrEmpty(userXml["email"].AsText) ? (ret.Email ?? string.Empty) : userXml["email"].AsText; //Build the realname (exposed as 'fullname' in user xml) by saving it as '{firstname} {lastname}' string externalFirstName = userXml["firstname"].AsText ?? string.Empty; string externalLastName = userXml["lastname"].AsText ?? string.Empty; string separator = externalLastName.Length > 0 && externalFirstName.Length > 0 ? ", " : string.Empty; // NOTE (maxm): Fullname sync is disabled for now. Refer to bug 7855 #if !DISABLE_REAL_NAME_SYNCHRONIZATION ret.RealName = string.Format("{0}{1}{2}", externalLastName, separator, externalFirstName); #endif ret.ServiceId = serviceInfo.Id; ret.Touched = DateTime.UtcNow; ret.ExternalName = string.IsNullOrEmpty(ret.ExternalName) ? nameFromAuthProvider : ret.ExternalName; ret.Name = string.IsNullOrEmpty(ret.Name) ? nameFromAuthProvider : ret.Name; //For new users, the name must be normalized and unique if (ret.ID == 0) { string nameFromExternalName = string.Empty; //Allow using a displayname from an external provider only for new accounts if (!userXml["@displayname"].IsEmpty) { nameFromExternalName = userXml["@displayname"].AsText; } else { nameFromExternalName = ret.ExternalName; } ret.Name = UserBL.NormalizeExternalNameToWikiUsername(nameFromExternalName); } //Build group objects out of the user's group membership list externalGroups = new List <GroupBE>(); IList <GroupBE> userGroups = DbUtils.CurrentSession.Groups_GetByUser(ret.ID); //Preserve local groups for existing users if (ret.ID != 0 && userGroups != null) { foreach (GroupBE g in userGroups) { if (ServiceBL.IsLocalAuthService(g.ServiceId)) { externalGroups.Add(g); } } } foreach (XDoc group in userXml["groups/group"]) { GroupBE g = new GroupBE(); g.Name = group["@name"].AsText; g.ServiceId = serviceInfo.Id; if (!string.IsNullOrEmpty(g.Name)) { externalGroups.Add(g); } } } else { switch (response.Status) { case DreamStatus.Unauthorized: if (bypassAuthentication) { DekiContext.Current.Instance.Log.Warn(string.Format("Attempted to lookup user info on auth provider '{0}' but failed since it required credentials", serviceInfo.Id)); } throw new ExternalServiceAuthenticationDeniedException(DekiWikiService.AUTHREALM, serviceInfo.Description); default: throw new ExternalServiceResponseException(errMsg, response); } } return(ret); }
public static XDoc GetUserXmlVerbose(UserBE user, string relationAttr, bool showPrivateInfo, bool showGroups, bool showProperties) { XDoc userXml = GetUserXml(user, relationAttr, showPrivateInfo); userXml.Elem("date.created", user.CreateTimestamp); if (!IsAnonymous(user)) { PageBE homePage = GetHomePage(user); if (homePage != null && homePage.ID != 0) { userXml.Add(PageBL.GetPageXml(homePage, "home")); } } userXml.Start("status").Value(user.UserActive ? "active" : "inactive").End(); userXml.Start("date.lastlogin").Value(user.Touched).End(); userXml.Start("language").Value(user.Language).End(); userXml.Start("timezone").Value(user.Timezone).End(); ServiceBE authService = ServiceBL.GetServiceById(user.ServiceId); if (authService != null) { userXml.Add(ServiceBL.GetServiceXml(authService, "authentication")); } //Permissions for the user from user role userXml.Add(PermissionsBL.GetRoleXml(PermissionsBL.GetRoleById(user.RoleId), "user")); ulong effectivePermissions = PermissionsBL.CalculateEffectiveUserRights(user); //Effective permissions for the user from the role + group roles. userXml.Add(PermissionsBL.GetPermissionXml(effectivePermissions, "effective")); // Set of permissions revoked from the user userXml.Add(PermissionsBL.GetPermissionsRevokedXml(user)); // check if groups should be included if (showGroups) { userXml.Start("groups"); IList <GroupBE> groups = DbUtils.CurrentSession.Groups_GetByUser(user.ID); if (null != groups) { foreach (GroupBE g in groups) { userXml.Add(GroupBL.GetGroupXmlVerbose(g, null)); } } userXml.End(); } // retrieve properties for current user while providing an href for other users. if (showProperties && (DekiContext.Current != null && DekiContext.Current.User != null && DekiContext.Current.User.ID == user.ID)) { IList <ResourceBE> props = PropertyBL.Instance.GetUserProperties(user.ID); userXml = PropertyBL.Instance.GetPropertyXml(props, GetUri(user), null, null, userXml); } else { userXml.Start("properties").Attr("href", GetUri(user).At("properties")).End(); } // TODO Max: get <subscriptions> (watchlist) not implemented return(userXml); }
private static void ParseUserXml(XDoc userDoc, out uint?id, out string username, out string email, out string fullname, out ServiceBE authService, out RoleBE role, out bool?active, out string language, out string timezone) { username = userDoc["username"].AsText; email = userDoc["email"].AsText; fullname = userDoc["fullname"].AsText; language = userDoc["language"].AsText; timezone = userDoc["timezone"].AsText; string authserviceidstr = userDoc["service.authentication/@id"].AsText; string rolestr = userDoc["permissions.user/role"].AsText; string statusStr = userDoc["status"].AsText; authService = null; role = null; id = null; if (!userDoc["@id"].IsEmpty) { uint id_temp; if (!uint.TryParse(userDoc["@id"].Contents, out id_temp)) { throw new UserIdAttrInvalidArgumentException(); } id = id_temp; } if (!string.IsNullOrEmpty(authserviceidstr)) { uint serviceid; if (!uint.TryParse(authserviceidstr, out serviceid)) { throw new ServiceAuthIdAttrInvalidArgumentException(); } authService = ServiceBL.GetServiceById(serviceid); if (authService == null) { throw new ServiceDoesNotExistInvalidArgumentException(serviceid); } } if (!string.IsNullOrEmpty(rolestr)) { role = PermissionsBL.GetRoleByName(rolestr); if (role == null) { throw new RoleDoesNotExistInvalidArgumentException(rolestr); } } if (!string.IsNullOrEmpty(statusStr)) { switch (statusStr.ToLowerInvariant()) { case "active": active = true; break; case "inactive": active = false; break; default: throw new UserStatusAttrInvalidArgumentException(); } } else { active = null; } if (!string.IsNullOrEmpty(timezone)) { if (!timeZoneRegex.Match(timezone).Success) { throw new UserTimezoneInvalidArgumentException(); } } if (!string.IsNullOrEmpty(language)) { string[] validLanguages = DekiContext.Current.Instance.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); string tempLanguage = language; if (!Array.Exists(validLanguages, delegate(string temp) { return(temp.EqualsInvariantIgnoreCase(tempLanguage)); })) { throw new UserInvalidLanguageException(); } } }
private static UserBE UpdateUserFromXml(UserBE userToUpdate, XDoc userDoc, string username, string email, string fullname, ServiceBE authservice, RoleBE role, bool?active, string externalusername, string externalpassword, string language, string timezone, out List <GroupBE> externalGroups) { externalGroups = null; if (userToUpdate.Name != username && !string.IsNullOrEmpty(username)) { if (UserBL.IsAnonymous(userToUpdate)) { throw new UserAnonymousEditInvalidOperationException(); } userToUpdate = RenameUser(userToUpdate, username, fullname ?? userToUpdate.RealName); } //Modify a user's authentication service if (authservice != null && authservice.Id != userToUpdate.ServiceId) { if (UserBL.IsAnonymous(userToUpdate)) { throw new UserAnonymousEditInvalidOperationException(); } if (ServiceBL.IsLocalAuthService(authservice)) { //external to local userToUpdate.ExternalName = null; userToUpdate.ServiceId = authservice.Id; } else { //(local or external) to external userToUpdate = ExternalServiceSA.BuildUserFromAuthService(authservice, userToUpdate, userToUpdate.Name, true, externalusername, externalpassword, out externalGroups); if (userToUpdate == null) { throw new UserAuthChangeFatalException(); } //Does the external account already exist? UserBE matchingExternalAccount = DbUtils.CurrentSession.Users_GetByExternalName(userToUpdate.ExternalName, userToUpdate.ServiceId); if (matchingExternalAccount != null) { throw new ExternalUserExistsConflictException(matchingExternalAccount.Name, matchingExternalAccount.ExternalName, matchingExternalAccount.ServiceId); } } } if (email != null) { if (UserBL.IsAnonymous(userToUpdate) && email != userToUpdate.Email) { throw new UserAnonymousEditInvalidOperationException(); } userToUpdate.Email = email; } if (!string.IsNullOrEmpty(fullname)) { userToUpdate.RealName = fullname; } if (active != null) { // disabling user if (userToUpdate.UserActive && !active.Value) { // cannot disable anonymous user if (UserBL.IsAnonymous(userToUpdate)) { throw new UserAnonymousDeactivationInvalidOperationException(); } // cannot disable owner if (DekiContext.Current.LicenseManager.GetSiteOwnerUserId().GetValueOrDefault(0) == userToUpdate.ID) { throw new UserOwnerDeactivationConflict(); } } //throw exception if licensing does not allow activating a user if (!userToUpdate.UserActive && active.Value) { DekiContext.Current.LicenseManager.IsUserCreationAllowed(true); } userToUpdate.UserActive = active.Value; } if (role != null && role.ID != userToUpdate.RoleId) { PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN); userToUpdate.RoleId = role.ID; } if (language != null) { userToUpdate.Language = language; } if (timezone != null) { userToUpdate.Timezone = timezone; } return(userToUpdate); }
public static UserBE PostUserFromXml(XDoc userDoc, UserBE userToProcess, string accountpassword, string externalusername, string externalpassword) { List <GroupBE> externalGroups = null; uint? id; bool? active; string username, fullname, email, language, timezone; ServiceBE authService; RoleBE role; // parse the standard user XML doc ParseUserXml(userDoc, out id, out username, out email, out fullname, out authService, out role, out active, out language, out timezone); // new user if ((userToProcess == null) && ((id == null) || (id == 0))) { userToProcess = ReadUserXml(userDoc, username, email, fullname, authService, role, language, timezone); // external accounts should be confirmed, username normalized, groups retrieved if ((authService != null) && !ServiceBL.IsLocalAuthService(authService)) { // check if a local-account password was provided if (!string.IsNullOrEmpty(accountpassword)) { throw new ExternalUserPasswordInvalidOperationException(); } // only admins can create external accounts for others. Anyone can create their own external account if (externalusername != userToProcess.Name || externalusername == string.Empty) { PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN); } // username+password from request query params are used here userToProcess = ExternalServiceSA.BuildUserFromAuthService(authService, userToProcess, username, true, externalusername, externalpassword, out externalGroups); if (userToProcess == null) { throw new ExternalUserNotFoundException(username); } // does the external account already exist? UserBE matchingExternalAccount = DbUtils.CurrentSession.Users_GetByExternalName(userToProcess.ExternalName, userToProcess.ServiceId); if (matchingExternalAccount != null) { throw new ExternalUserExistsConflictException(matchingExternalAccount.Name, userToProcess.ExternalName, userToProcess.ServiceId); } } else { // creating local account // user creation requires admin rights unless the config flag allows it // anonymous users are not allowed to set role if (!DekiContext.Current.Instance.AllowAnonymousLocalAccountCreation || role != null) { PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN); } } // sanity check for already existing user UserBE existingUser = DbUtils.CurrentSession.Users_GetByName(userToProcess.Name); if (existingUser != null) { throw new UserWithIdExistsConflictException(existingUser.Name, existingUser.ID); } //if (UserDA.RetrieveUserRegistrations(userToProcess.Name)) { // throw new DreamAbortException(DreamMessage.Conflict(string.Format("User '{0}' has been reserved", userToProcess.Name))); //} userToProcess = CreateOrUpdateUser(userToProcess, accountpassword); if (null != externalGroups) { UpdateUsersGroups(userToProcess, externalGroups.ToArray()); } } else { // update existing user if (userToProcess == null) { userToProcess = GetUserById(id.Value); if (userToProcess == null) { throw new UserIdNotFoundException(id.Value); } } // TODO (steveb): either this needs to go or the definition for PUT:user/{userid} should not mention the 'accountpassword' parameter if (!string.IsNullOrEmpty(accountpassword)) { throw new UserUsePutToChangePasswordInvalidOperationException(); } // permission check if not modifying self if (userToProcess.ID != DekiContext.Current.User.ID) { PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN); } userToProcess = UpdateUserFromXml(userToProcess, userDoc, username, email, fullname, authService, role, active, externalusername, externalpassword, language, timezone, out externalGroups); userToProcess = CreateOrUpdateUser(userToProcess); if (null != externalGroups) { UpdateUsersGroups(userToProcess, externalGroups.ToArray()); } if (IsAnonymous(userToProcess) && DekiContext.Current.Instance.CacheAnonymousOutput) { DekiContext.Current.Deki.EmptyResponseCacheInternal(); } try { if (!userToProcess.UserActive && userToProcess.LicenseSeat && DekiContext.Current.LicenseManager.IsSeatLicensingEnabled()) { DekiContext.Current.LicenseManager.RemoveSeatFromUser(userToProcess); } } catch (Exception x) { _log.WarnExceptionFormat(x, "Unable to remove license seat for disabled user. user id: {0}", userToProcess.ID); } } return(userToProcess); }
public static GroupBE PostGroupFromXml(XDoc groupDoc, GroupBE groupToProcess, string externalusername, string externalpassword) { GroupBE group = null; string groupName = string.Empty; ServiceBE groupService = null; RoleBE groupRole = null; UserBE[] groupMembers = null; uint? groupId = null; ParseGroupXml(groupDoc, out groupId, out groupName, out groupService, out groupRole, out groupMembers); //Create new group if (groupToProcess == null && (groupId == null || groupId == 0)) { if (groupService == null) { groupService = ServiceBL.RetrieveLocalAuthService(); } //External groups should be confirmed with the auth provider if (groupService != null && !ServiceBL.IsLocalAuthService(groupService)) { //username+password from request query params are used here group = ExternalServiceSA.BuildGroupFromAuthService(groupService, groupToProcess, groupName, externalusername, externalpassword); if (group == null) { throw new ExternalGroupNotFoundException(groupName); } } //Does this group already exist? GroupBE tempGroup = GetGroupByName(groupName); if (tempGroup != null) { throw new GroupExistsWithServiceConflictException(groupName, tempGroup.ServiceId); } ValidateGroupMemberList(groupService, groupMembers); // Insert the group GroupBE newGroup = new GroupBE(); newGroup.Name = groupName; newGroup.RoleId = groupRole.ID; newGroup.ServiceId = groupService.Id; newGroup.CreatorUserId = DekiContext.Current.User.ID; newGroup.TimeStamp = DateTime.UtcNow; uint newGroupId = DbUtils.CurrentSession.Groups_Insert(newGroup); if (newGroupId == 0) { group = null; } else { DbUtils.CurrentSession.GroupMembers_UpdateUsersInGroup(newGroupId, groupMembers.Select(e => e.ID).ToList(), newGroup.TimeStamp); // reload the group to ensure group members are set group = GetGroupById(newGroupId); } } //Edit existing group else { if (groupId != null) { groupToProcess = GetGroupById(groupId.Value); } if (groupToProcess == null) { throw new GroupIdNotFoundException(groupId); } group = groupToProcess; //Change the role? if (group.RoleId != groupRole.ID) { group.RoleId = groupRole.ID; } //Rename the group? if (group.Name != groupName && !string.IsNullOrEmpty(groupName)) { GroupBE tempGroup = GetGroupByName(groupName); if (tempGroup != null) { throw new GroupExistsWithServiceConflictException(groupName, tempGroup.ServiceId); } if (!ServiceBL.IsLocalAuthService(group.ServiceId)) { //TODO MaxM: allow renaming of external groups throw new ExternalGroupRenameNotImplementedException(); } //Set the new name of the group. group.Name = groupName; } DbUtils.CurrentSession.Groups_Update(group); //TODO (MaxM): Update group list as well? group = GetGroupById(group.Id); } if (group == null) { throw new GroupCreateUpdateFatalException(); } return(group); }