/// <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
            });
        }
예제 #3
0
        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"));
        }
예제 #4
0
        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
                });
            }
        }
예제 #7
0
        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
            });
        }