/// <summary> /// Verify admin permission by access token /// </summary> /// <param name="authToken">Authencation token</param> /// <returns>IActionResult if it has error. Otherwise return null</returns> public static async Task <IActionResult> VerifyAdminToken(string authToken) { // validate auth token if (string.IsNullOrWhiteSpace(authToken)) { return(CreateErrorResponse("auth_token is missing", StatusCodes.Status401Unauthorized)); } var(result, message, id) = await ADAccess.Instance.ValidateAccessToken(authToken); if (!result || string.IsNullOrWhiteSpace(id)) { return(CreateErrorResponse("auth_token is invalid", StatusCodes.Status401Unauthorized)); } var user = await ADUser.FindById(id); // make sure user is in admin group var adminGroup = await ADGroup.FindByName(Configurations.AzureB2C.AdminGroup); var isMemberOf = await adminGroup.HasUser(user.ObjectId); if (!isMemberOf) { return(CreateErrorResponse("Insufficient privileges", StatusCodes.Status401Unauthorized)); } return(null); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { Logger.Log = log; // validate b2c refresh token string refreshToken = req.Query["refresh_token"]; if (string.IsNullOrWhiteSpace(refreshToken)) { // default is guest var guestGroup = await ADGroup.FindByName(Configurations.AzureB2C.GuestGroup); // If the refresh token is missing, then return permissions for guest var guestPermissions = await guestGroup.GetPermissions(); return(new JsonResult(new { success = true, permissions = guestPermissions, group = guestGroup.Name }) { StatusCode = StatusCodes.Status200OK }); } string source = req.Query["source"]; ADGroup userGroup = null; ADUser user; ADToken adToken; // cognito authentication if (source == "cognito") { adToken = await CognitoService.Instance.GetAccessToken(refreshToken); if (adToken == null || string.IsNullOrWhiteSpace(adToken.AccessToken)) { return(CreateErrorResponse($"refresh_token is invalid: {refreshToken} ", StatusCodes.Status401Unauthorized)); } // Validate the access token, then get id and group name var(result, message, userId, groupName) = await CognitoService.Instance.ValidateAccessToken(adToken.AccessToken); if (!result) { log.LogError($"can not get access token from refresh token {refreshToken}"); return(CreateErrorResponse(message, StatusCodes.Status403Forbidden)); } var customUserId = await CognitoService.Instance.GetCustomUserId(userId); if (string.IsNullOrWhiteSpace(customUserId)) { return(CreateErrorResponse($"user {userId} does not have custom id", statusCode: StatusCodes.Status500InternalServerError)); } // NOTE: if cognito user is disable, it throws exception on refresh token step above, so may not need to check account status //var userInfo = await CognitoService.Instance.GetUserInfo(userId); //if (!userInfo.Enabled) //{ // return CreateErrorResponse("user is disabled", statusCode: StatusCodes.Status401Unauthorized); //} // create fake ADUser and ADGroup from cognito information user = new ADUser { ObjectId = customUserId }; userGroup = new ADGroup { Name = groupName }; } else { // azure b2c authentication // get access token by refresh token adToken = await ADAccess.Instance.RefreshToken(refreshToken); if (adToken == null || string.IsNullOrWhiteSpace(adToken.AccessToken)) { return(CreateErrorResponse($"refresh_token is invalid: {refreshToken} ", StatusCodes.Status401Unauthorized)); } // Validate the access token, then get id var(result, message, id) = await ADAccess.Instance.ValidateAccessToken(adToken.AccessToken); if (!result) { log.LogError($"can not get access token from refresh token {refreshToken}"); return(CreateErrorResponse(message, StatusCodes.Status403Forbidden)); } // find ad user by its email user = await ADUser.FindById(id); if (user == null) { return(CreateErrorResponse("user not exist")); } if (!user.AccountEnabled) { return(CreateErrorResponse("user is disabled", statusCode: StatusCodes.Status401Unauthorized)); } // check role of user var groupIds = await user.GroupIds(); if (groupIds != null && groupIds.Count > 0) { var group = await ADGroup.FindById(groupIds[0]); if (group != null) { userGroup = group; } } if (userGroup == null) { userGroup = await ADGroup.FindByName(Configurations.AzureB2C.GuestGroup); } } log.LogInformation($"user {user?.ObjectId} has group {userGroup?.Name}"); var tasks = new List <Task <List <PermissionProperties> > >(); // get group permissions tasks.Add(userGroup.GetPermissions()); // get user permissions tasks.Add(user.GetPermissions(userGroup.Name)); await Task.WhenAll(tasks); var permissions = new List <PermissionProperties>(); foreach (var task in tasks) { var p = task.Result; permissions.AddRange(p); } // return list of permissions return(new JsonResult(new { success = true, permissions, group = userGroup.Name, refreshToken = adToken.RefreshToken }) { StatusCode = StatusCodes.Status200OK }); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { Logger.Log = log; if (!string.IsNullOrWhiteSpace(req.Query["refresh_token"])) { // get access token by refresh token var adToken = await ADAccess.Instance.RefreshToken(req.Query["refresh_token"]); if (adToken == null || string.IsNullOrWhiteSpace(adToken.AccessToken)) { return(CreateErrorResponse("refresh token is invalid", StatusCodes.Status401Unauthorized)); } // validate admin token var actionResult = await VerifyAdminToken(adToken.AccessToken); if (actionResult != null) { return(actionResult); } } else { // validate auth token var actionResult = await VerifyAdminToken(req.Query["auth_token"]); if (actionResult != null) { return(actionResult); } } // validate user email string email = req.Query["email"]; // validate email address if (string.IsNullOrWhiteSpace(email) || !email.IsValidEmailAddress()) { return(CreateErrorResponse("Email is invalid")); } // validate role parameter var group = await ADGroup.FindByName(req.Query["role"]); if (group == null) { return(CreateErrorResponse("Role is invalid")); } // replace space by + to correct because email contains "+" will be encoded by space, like "*****@*****.**" -> "a [email protected]" email = email.Trim().Replace(" ", "+"); string name = email.GetNameFromEmail(); // create user if need var(_, user) = await ADUser.FindOrCreate(email, name); // there is an error when creating user if (user == null) { return(CreateErrorResponse($"can not create user {email}", StatusCodes.Status500InternalServerError)); } var result = await user.UpdateGroup(group.Name); string language = req.Query["language"]; if (!string.IsNullOrWhiteSpace(language)) { var cosmosUser = await User.GetById(user.ObjectId); if (cosmosUser == null) { cosmosUser = new User { Id = user.ObjectId, Email = email, Language = language, }; await cosmosUser.CreateOrUpdate(); } else if (cosmosUser.Language != language) { cosmosUser.Language = language; await cosmosUser.CreateOrUpdate(); } } if (result) { return(CreateSuccessResponse()); } return(CreateErrorResponse("can not add user into group")); }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { // Set the logger instance Logger.Log = log; string email = req.Query["email"]; // validate email address if (string.IsNullOrWhiteSpace(email)) { return(CreateErrorResponse($"Email is empty")); } if (!email.IsValidEmailAddress()) { return(CreateErrorResponse($"Email {email} is invalid")); } var ipAddress = HttpHelper.GetIpFromRequestHeaders(req); string country = req.Query["country"]; // Fix the encode issue because email parameter that contains "+" will be encoded by space // e.g. client sends "*****@*****.**" => Azure function read: "a [email protected]" (= req.Query["email"]) // We need to replace space by "+" when reading the parameter req.Query["email"] // Then the result is correct "*****@*****.**" email = email.Trim().Replace(" ", "+"); string name = email.GetNameFromEmail(); log.LogInformation($"Check account for user ${email}"); // check if email is existed in b2c. If it is, return that user var(exist, user) = await ADUser.FindOrCreate(email, name, country, ipAddress); // there is an error when creating user if (user == null) { return(CreateErrorResponse($"can not create user {email}", StatusCodes.Status500InternalServerError)); } // if user already has account if (exist) { // update country and ipadress if needed var updateParams = new Dictionary <string, string>(); if (!string.IsNullOrWhiteSpace(country)) { updateParams["country"] = country; user.Country = country; } if (!string.IsNullOrWhiteSpace(ipAddress)) { updateParams["streetAddress"] = ipAddress; user.IPAddress = ipAddress; } if (updateParams.Count > 0) { await user.Update(updateParams); } log.LogInformation($"User ${email} exists, {ipAddress}, {country}"); return(new JsonResult(new { success = true, exist, user }) { StatusCode = StatusCodes.Status200OK }); } else { log.LogInformation($"User ${email} not exists, try to create a new one with {ipAddress}, {country}"); try { await SendAnalytics(email, user.ObjectId, country, ipAddress, name); } catch (Exception ex) { if (!(ex is TimeoutException)) { log.LogError($"Send analytics error {ex.Message}"); } } } // add user to new group var newGroup = await ADGroup.FindByName("new"); var addResult = await newGroup.AddUser(user.ObjectId); // there is an error when add user into new group if (!addResult) { return(CreateErrorResponse($"can not add user {email} into new group", StatusCodes.Status500InternalServerError)); } // Success, return user info return(new JsonResult(new { success = true, exist, user }) { StatusCode = StatusCodes.Status200OK }); }
public async Task <(bool, UserType)> FindOrCreateUser(string email, string name, string country, string ipAddress, ADUser adUser = null, bool shouldUpdateAdUser = true) { email = email.ToLower(); var request = new ListUsersRequest { UserPoolId = Configurations.Cognito.CognitoPoolId, Filter = $"email = \"{email}\"", }; var usersResponse = await provider.ListUsersAsync(request); if (usersResponse.Users.Count > 0) { var user = usersResponse.Users.First(); // dont return passcode property to client user.Attributes.Remove(user.Attributes.Find(x => x.Name == "custom:authChallenge")); return(true, user); } else { var adUserExisting = false; if (adUser == null) { // create AD User if needed var(existing, newAdUser) = await ADUser.FindOrCreate(email, name, country, ipAddress); adUser = newAdUser; adUserExisting = existing; } else { adUserExisting = true; } if (adUser == null) { throw new Exception($"can not create ad user {email}"); } // update new properties if (adUserExisting && shouldUpdateAdUser) { var updateParams = new Dictionary <string, dynamic>(); if (!string.IsNullOrWhiteSpace(country)) { updateParams["country"] = country; adUser.Country = country; } if (!string.IsNullOrWhiteSpace(ipAddress)) { updateParams["streetAddress"] = ipAddress; adUser.IPAddress = ipAddress; } // enable account if needed if (!adUser.AccountEnabled) { updateParams["accountEnabled"] = true; adUser.AccountEnabled = true; } if (updateParams.Count > 0) { await adUser.Update(updateParams); } } // then create cognito user var attributes = new List <AttributeType>(); if (!string.IsNullOrWhiteSpace(name)) { attributes.Add(new AttributeType() { Name = "name", Value = name }); } if (!string.IsNullOrWhiteSpace(country)) { attributes.Add(new AttributeType() { Name = "custom:country", Value = country }); } else if (!string.IsNullOrWhiteSpace(adUser.Country)) { attributes.Add(new AttributeType() { Name = "custom:country", Value = adUser.Country }); } if (!string.IsNullOrWhiteSpace(ipAddress)) { attributes.Add(new AttributeType() { Name = "custom:ipAddress", Value = ipAddress }); } else if (!string.IsNullOrWhiteSpace(adUser.IPAddress)) { attributes.Add(new AttributeType() { Name = "custom:ipAddress", Value = adUser.IPAddress }); } // set custom user id from b2c attributes.Add(new AttributeType() { Name = "preferred_username", Value = adUser.ObjectId }); attributes.Add(new AttributeType() { Name = "email", Value = email }); // create new user with temp password var createRequest = new AdminCreateUserRequest { UserPoolId = Configurations.Cognito.CognitoPoolId, Username = email, UserAttributes = attributes, TemporaryPassword = TokenService.GeneratePassword(Guid.NewGuid().ToString()), MessageAction = MessageActionType.SUPPRESS, }; UserType newUser; try { var createUserResponse = await provider.AdminCreateUserAsync(createRequest); newUser = createUserResponse.User; } catch (UsernameExistsException ex) { // TODO will remove later (after fixing from client) Logger.Log?.LogError($"user name exist {ex.Message}"); // user exist in other request, just get it from cognito after few second Task.Delay(5 * 1000).Wait(); usersResponse = await provider.ListUsersAsync(request); newUser = usersResponse.Users.First(); } // then change its password var changePasswordRequest = new AdminSetUserPasswordRequest { UserPoolId = Configurations.Cognito.CognitoPoolId, Username = newUser.Username, Password = TokenService.GeneratePassword(email), Permanent = true }; await provider.AdminSetUserPasswordAsync(changePasswordRequest); if (!adUserExisting) { // add cognito user into group new await UpdateUserGroup(newUser.Username, "new"); if (shouldUpdateAdUser) { // add ad user into group new var newGroup = await ADGroup.FindByName("new"); var addResult = await newGroup.AddUser(adUser.ObjectId); if (!addResult) { throw new Exception($"can not add ad user {email} into new group"); } } } else { // add cognito user into group from b2c var groupName = await adUser.GroupName(); if (!string.IsNullOrWhiteSpace(groupName)) { await UpdateUserGroup(newUser.Username, groupName); } else { Logger.Log?.LogError($"user {email} does not have group"); } } // dont return passcode property to client newUser.Attributes.Remove(newUser.Attributes.Find(x => x.Name == "custom:authChallenge")); return(false, newUser); } }
private async Task <IActionResult> ProcessAzureB2cRequest(ILogger log, string email, string name, string country, string ipAddress) { // check if email is existed in b2c. If it is, return that user var(exist, user) = await ADUser.FindOrCreate(email, name, country, ipAddress); // there is an error when creating user if (user == null) { return(CreateErrorResponse($"can not create user {email}", StatusCodes.Status500InternalServerError)); } // if user already has account if (exist) { // update country and ipadress if needed var updateParams = new Dictionary <string, dynamic>(); if (!string.IsNullOrWhiteSpace(country)) { updateParams["country"] = country; user.Country = country; } if (!string.IsNullOrWhiteSpace(ipAddress)) { updateParams["streetAddress"] = ipAddress; user.IPAddress = ipAddress; } // enable account if needed if (!user.AccountEnabled) { updateParams["accountEnabled"] = true; user.AccountEnabled = true; } if (updateParams.Count > 0) { await user.Update(updateParams); } log.LogInformation($"User ${email} exists, {ipAddress}, {country}"); await CognitoService.Instance.FindOrCreateUser(email, name, country, ipAddress, user, shouldUpdateAdUser : false); return(new JsonResult(new { success = true, exist, user }) { StatusCode = StatusCodes.Status200OK }); } else { log.LogInformation($"User ${email} not exists, try to create a new one with {ipAddress}, {country}"); if (!email.EndsWith(Configurations.AzureB2C.EmailTestDomain)) { try { await SendAnalytics(email, user.ObjectId, country, ipAddress, name); } catch (Exception ex) { if (!(ex is TimeoutException)) { log.LogError($"Send analytics error {ex.Message}"); } } } else { log.LogInformation($"User ${email} is a test user, skip sending analytics"); } // add user to new group var newGroup = await ADGroup.FindByName("new"); var addResult = await newGroup.AddUser(user.ObjectId); // there is an error when add user into new group if (!addResult) { return(CreateErrorResponse($"can not add user {email} into new group", StatusCodes.Status500InternalServerError)); } await CognitoService.Instance.FindOrCreateUser(email, name, country, ipAddress, user, shouldUpdateAdUser : false); // Success, return user info return(new JsonResult(new { success = true, exist, user }) { StatusCode = StatusCodes.Status200OK }); } }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { Logger.Log = log; // validate b2c refresh token string refreshToken = req.Query["refresh_token"]; if (string.IsNullOrWhiteSpace(refreshToken)) { // default is guest var guestGroup = await ADGroup.FindByName(Configurations.AzureB2C.GuestGroup); // If the refresh token is missing, then return permissions for guest var guestPermissions = await guestGroup.GetPermissions(); return(new JsonResult(new { success = true, permissions = guestPermissions, group = guestGroup.Name }) { StatusCode = StatusCodes.Status200OK }); } // get access token by refresh token var adToken = await ADAccess.Instance.RefreshToken(refreshToken); if (adToken == null || string.IsNullOrWhiteSpace(adToken.AccessToken)) { return(CreateErrorResponse($"refresh_token is invalid: {refreshToken} ", StatusCodes.Status401Unauthorized)); } // Validate the access token, then get id var(result, message, id) = await ADAccess.Instance.ValidateAccessToken(adToken.AccessToken); if (!result) { log.LogError($"can not get access token from refresh token {refreshToken}"); return(CreateErrorResponse(message, StatusCodes.Status403Forbidden)); } // find ad user by its email var user = await ADUser.FindById(id); if (user == null) { return(CreateErrorResponse("user not exist")); } // check role of user ADGroup userGroup = null; var groupIds = await user.GroupIds(); if (groupIds != null && groupIds.Count > 0) { var group = await ADGroup.FindById(groupIds[0]); if (group != null) { userGroup = group; } } if (userGroup == null) { userGroup = await ADGroup.FindByName(Configurations.AzureB2C.GuestGroup); } log.LogInformation($"user {user?.ObjectId} has group {userGroup?.Name}"); // get group permissions var permissions = await userGroup.GetPermissions(); // get user permissions var userPermissions = await user.GetPermissions(userGroup.Name); permissions.AddRange(userPermissions); // return list of permissions return(new JsonResult(new { success = true, permissions, group = userGroup.Name, refreshToken = adToken.RefreshToken }) { StatusCode = StatusCodes.Status200OK }); }