Ejemplo n.º 1
0
        public async Task <Organization> AddOrganizationAsync(string username, string emailAddress, User adminUser)
        {
            if (!ContentObjectService.LoginDiscontinuationConfiguration.AreOrganizationsSupportedForUser(adminUser))
            {
                throw new EntityException(String.Format(CultureInfo.CurrentCulture,
                                                        Strings.Organizations_NotInDomainWhitelist, adminUser.Username));
            }

            var existingUserWithIdentity = EntitiesContext.Users
                                           .FirstOrDefault(u => u.Username == username || u.EmailAddress == emailAddress);

            if (existingUserWithIdentity != null)
            {
                if (existingUserWithIdentity.Username.Equals(username, StringComparison.OrdinalIgnoreCase))
                {
                    throw new EntityException(Strings.UsernameNotAvailable, username);
                }

                if (string.Equals(existingUserWithIdentity.EmailAddress, emailAddress, StringComparison.OrdinalIgnoreCase))
                {
                    throw new EntityException(Strings.EmailAddressBeingUsed, emailAddress);
                }
            }

            var organization = new Organization(username)
            {
                EmailAllowed            = true,
                UnconfirmedEmailAddress = emailAddress,
                EmailConfirmationToken  = Crypto.GenerateToken(),
                NotifyPackagePushed     = true,
                CreatedUtc = DateTimeProvider.UtcNow,
                Members    = new List <Membership>()
            };

            var membership = new Membership {
                Organization = organization, Member = adminUser, IsAdmin = true
            };

            organization.Members.Add(membership);
            adminUser.Organizations.Add(membership);

            OrganizationRepository.InsertOnCommit(organization);

            if (string.IsNullOrEmpty(GetAzureActiveDirectoryCredentialTenant(adminUser)))
            {
                throw new EntityException(String.Format(CultureInfo.CurrentCulture,
                                                        Strings.Organizations_AdminAccountDoesNotHaveTenant, adminUser.Username));
            }

            if (!await SubscribeOrganizationToTenantPolicy(organization, adminUser, commitChanges: false))
            {
                throw new EntityException(Strings.DefaultUserSafeExceptionMessage);
            }

            await EntitiesContext.SaveChangesAsync();

            return(organization);
        }
Ejemplo n.º 2
0
        public async Task <Organization> AddOrganizationAsync(string username, string emailAddress, User adminUser)
        {
            var existingUserWithIdentity = EntitiesContext.Users
                                           .FirstOrDefault(u => u.Username == username || u.EmailAddress == emailAddress);

            if (existingUserWithIdentity != null)
            {
                if (existingUserWithIdentity.Username.Equals(username, StringComparison.OrdinalIgnoreCase))
                {
                    throw new EntityException(Strings.UsernameNotAvailable, username);
                }

                if (string.Equals(existingUserWithIdentity.EmailAddress, emailAddress, StringComparison.OrdinalIgnoreCase))
                {
                    throw new EntityException(Strings.EmailAddressBeingUsed, emailAddress);
                }
            }

            var organization = new Organization(username)
            {
                EmailAllowed            = true,
                UnconfirmedEmailAddress = emailAddress,
                EmailConfirmationToken  = Crypto.GenerateToken(),
                NotifyPackagePushed     = true,
                CreatedUtc = DateTimeProvider.UtcNow,
                Members    = new List <Membership>()
            };

            if (!Config.ConfirmEmailAddresses)
            {
                organization.ConfirmEmailAddress();
            }

            var membership = new Membership {
                Organization = organization, Member = adminUser, IsAdmin = true
            };

            organization.Members.Add(membership);
            adminUser.Organizations.Add(membership);

            OrganizationRepository.InsertOnCommit(organization);

            await SubscribeOrganizationToTenantPolicyIfTenantIdIsSupported(organization, adminUser, commitChanges : false);

            await Auditing.SaveAuditRecordAsync(new UserAuditRecord(organization, AuditedUserAction.AddOrganization, membership));

            await EntitiesContext.SaveChangesAsync();

            return(organization);
        }
Ejemplo n.º 3
0
        public async Task <Membership> AddMemberAsync(Organization organization, string memberName, bool isAdmin)
        {
            organization = organization ?? throw new ArgumentNullException(nameof(organization));

            var membership = FindMembershipByUsername(organization, memberName);

            if (membership != null)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_AlreadyAMember, memberName));
            }

            var member = FindByUsername(memberName);

            if (member == null)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_UserNotFound, memberName));
            }

            if (!member.Confirmed)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_UserNotConfirmed, memberName));
            }

            // Ensure that the new member meets the AAD tenant policy for this organization.
            var policyResult = await SecurityPolicyService.EvaluateOrganizationPoliciesAsync(
                SecurityPolicyAction.JoinOrganization, organization, member);

            if (policyResult != SecurityPolicyResult.SuccessResult)
            {
                throw new EntityException(policyResult.ErrorMessage);
            }

            membership = new Membership()
            {
                Member  = member,
                IsAdmin = isAdmin
            };
            organization.Members.Add(membership);

            await EntitiesContext.SaveChangesAsync();

            return(membership);
        }
Ejemplo n.º 4
0
        private async Task <User> DeleteMembershipRequestHelperAsync(Organization organization, string memberName, string confirmationToken = null)
        {
            organization = organization ?? throw new ArgumentNullException(nameof(organization));

            var request = FindMembershipRequestByUsername(organization, memberName);

            if (request == null || (confirmationToken != null && request.ConfirmationToken != confirmationToken))
            {
                throw new InvalidOperationException("No such membership request exists!");
            }

            var pendingMember = request.NewMember;

            organization.MemberRequests.Remove(request);
            await EntitiesContext.SaveChangesAsync();

            return(pendingMember);
        }
Ejemplo n.º 5
0
        public async Task DeleteMemberAsync(Organization organization, string memberName)
        {
            organization = organization ?? throw new ArgumentNullException(nameof(organization));

            var membership = FindMembershipByUsername(organization, memberName);

            if (membership == null)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.UpdateOrDeleteMember_MemberNotFound, memberName));
            }

            // block removal of last admin
            if (membership.IsAdmin && organization.Administrators.Count() == 1)
            {
                throw new EntityException(Strings.DeleteMember_CannotRemoveLastAdmin);
            }

            organization.Members.Remove(membership);
            await EntitiesContext.SaveChangesAsync();
        }
Ejemplo n.º 6
0
        private async Task <ActionResult> CreatePackageInternal()
        {
            // Get the user
            var user = GetCurrentUser();

            using (var packageStream = ReadPackageFromRequest())
            {
                try
                {
                    using (var archive = new ZipArchive(packageStream, ZipArchiveMode.Read, leaveOpen: true))
                    {
                        var reference = DateTime.UtcNow.AddDays(1); // allow "some" clock skew

                        var entryInTheFuture = archive.Entries.FirstOrDefault(
                            e => e.LastWriteTime.UtcDateTime > reference);

                        if (entryInTheFuture != null)
                        {
                            return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, string.Format(
                                                                        CultureInfo.CurrentCulture,
                                                                        Strings.PackageEntryFromTheFuture,
                                                                        entryInTheFuture.Name)));
                        }
                    }

                    using (var packageToPush = new PackageArchiveReader(packageStream, leaveStreamOpen: false))
                    {
                        NuspecReader nuspec = null;
                        try
                        {
                            nuspec = packageToPush.GetNuspecReader();
                        }
                        catch (Exception ex)
                        {
                            return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, string.Format(
                                                                        CultureInfo.CurrentCulture,
                                                                        Strings.UploadPackage_InvalidNuspec,
                                                                        ex.Message)));
                        }

                        if (nuspec.GetMinClientVersion() > Constants.MaxSupportedMinClientVersion)
                        {
                            return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, string.Format(
                                                                        CultureInfo.CurrentCulture,
                                                                        Strings.UploadPackage_MinClientVersionOutOfRange,
                                                                        nuspec.GetMinClientVersion())));
                        }

                        // Ensure that the user can push packages for this partialId.
                        var packageRegistration = PackageService.FindPackageRegistrationById(nuspec.GetId());
                        if (packageRegistration != null)
                        {
                            if (!packageRegistration.IsOwner(user))
                            {
                                return(new HttpStatusCodeWithBodyResult(HttpStatusCode.Forbidden,
                                                                        Strings.ApiKeyNotAuthorized));
                            }

                            // Check if a particular Id-Version combination already exists. We eventually need to remove this check.
                            string normalizedVersion = nuspec.GetVersion().ToNormalizedString();
                            bool   packageExists     =
                                packageRegistration.Packages.Any(
                                    p => String.Equals(
                                        p.NormalizedVersion,
                                        normalizedVersion,
                                        StringComparison.OrdinalIgnoreCase));

                            if (packageExists)
                            {
                                return(new HttpStatusCodeWithBodyResult(
                                           HttpStatusCode.Conflict,
                                           String.Format(CultureInfo.CurrentCulture, Strings.PackageExistsAndCannotBeModified,
                                                         nuspec.GetId(), nuspec.GetVersion().ToNormalizedStringSafe())));
                            }
                        }

                        var packageStreamMetadata = new PackageStreamMetadata
                        {
                            HashAlgorithm = Constants.Sha512HashAlgorithmId,
                            Hash          = CryptographyService.GenerateHash(packageStream.AsSeekableStream()),
                            Size          = packageStream.Length,
                        };

                        var package = await PackageService.CreatePackageAsync(
                            packageToPush,
                            packageStreamMetadata,
                            user,
                            commitChanges : false);

                        await AutoCuratePackage.ExecuteAsync(package, packageToPush, commitChanges : false);

                        await EntitiesContext.SaveChangesAsync();

                        using (Stream uploadStream = packageStream)
                        {
                            uploadStream.Position = 0;
                            await PackageFileService.SavePackageFileAsync(package, uploadStream.AsSeekableStream());

                            IndexingService.UpdatePackage(package);
                        }

                        MessageService.SendPackageAddedNotice(package,
                                                              Url.Action("DisplayPackage", "Packages", routeValues: new { id = package.PackageRegistration.Id, version = package.Version }, protocol: Request.Url.Scheme),
                                                              Url.Action("ReportMyPackage", "Packages", routeValues: new { id = package.PackageRegistration.Id, version = package.Version }, protocol: Request.Url.Scheme),
                                                              Url.Action("Account", "Users", routeValues: null, protocol: Request.Url.Scheme));

                        return(new HttpStatusCodeResult(HttpStatusCode.Created));
                    }
                }
                catch (InvalidPackageException ex)
                {
                    return(BadRequestForExceptionMessage(ex));
                }
                catch (InvalidDataException ex)
                {
                    return(BadRequestForExceptionMessage(ex));
                }
                catch (EntityException ex)
                {
                    return(BadRequestForExceptionMessage(ex));
                }
                catch (FrameworkException ex)
                {
                    return(BadRequestForExceptionMessage(ex));
                }
            }
        }
Ejemplo n.º 7
0
        private async Task <ActionResult> CreatePackageInternal()
        {
            // Get the user
            var user = GetCurrentUser();

            using (var packageStream = ReadPackageFromRequest())
            {
                try
                {
                    using (var packageToPush = new PackageArchiveReader(packageStream, leaveStreamOpen: false))
                    {
                        NuspecReader nuspec = null;
                        try
                        {
                            nuspec = packageToPush.GetNuspecReader();
                        }
                        catch (Exception ex)
                        {
                            return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, string.Format(
                                                                        CultureInfo.CurrentCulture,
                                                                        Strings.UploadPackage_InvalidNuspec,
                                                                        ex.Message)));
                        }

                        if (nuspec.GetMinClientVersion() > Constants.MaxSupportedMinClientVersion)
                        {
                            return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, string.Format(
                                                                        CultureInfo.CurrentCulture,
                                                                        Strings.UploadPackage_MinClientVersionOutOfRange,
                                                                        nuspec.GetMinClientVersion())));
                        }

                        // Ensure that the user can push packages for this partialId.
                        var packageRegistration = PackageService.FindPackageRegistrationById(nuspec.GetId());
                        if (packageRegistration != null)
                        {
                            if (!packageRegistration.IsOwner(user))
                            {
                                return(new HttpStatusCodeWithBodyResult(HttpStatusCode.Forbidden, Strings.ApiKeyNotAuthorized));
                            }

                            // Check if a particular Id-Version combination already exists. We eventually need to remove this check.
                            string normalizedVersion = nuspec.GetVersion().ToNormalizedString();
                            bool   packageExists     =
                                packageRegistration.Packages.Any(
                                    p => String.Equals(
                                        p.NormalizedVersion,
                                        normalizedVersion,
                                        StringComparison.OrdinalIgnoreCase));

                            if (packageExists)
                            {
                                return(new HttpStatusCodeWithBodyResult(
                                           HttpStatusCode.Conflict,
                                           String.Format(CultureInfo.CurrentCulture, Strings.PackageExistsAndCannotBeModified,
                                                         nuspec.GetId(), nuspec.GetVersion().ToNormalizedStringSafe())));
                            }
                        }

                        var packageStreamMetadata = new PackageStreamMetadata
                        {
                            HashAlgorithm = Constants.Sha512HashAlgorithmId,
                            Hash          = CryptographyService.GenerateHash(packageStream.AsSeekableStream()),
                            Size          = packageStream.Length,
                        };

                        var package = await PackageService.CreatePackageAsync(packageToPush, packageStreamMetadata, user, commitChanges : false);

                        await AutoCuratePackage.ExecuteAsync(package, packageToPush, commitChanges : false);

                        await EntitiesContext.SaveChangesAsync();

                        using (Stream uploadStream = packageStream)
                        {
                            uploadStream.Position = 0;
                            await PackageFileService.SavePackageFileAsync(package, uploadStream.AsSeekableStream());

                            IndexingService.UpdatePackage(package);
                        }

                        return(new HttpStatusCodeResult(HttpStatusCode.Created));
                    }
                }
                catch (InvalidDataException ex)
                {
                    return(new HttpStatusCodeWithBodyResult(
                               HttpStatusCode.BadRequest,
                               string.Format(CultureInfo.CurrentCulture, Strings.UploadPackage_InvalidPackage, ex.Message)));
                }
            }
        }
Ejemplo n.º 8
0
        private async Task <ActionResult> CreatePackageInternal()
        {
            // Get the user
            var user = GetCurrentUser();

            using (var packageStream = ReadPackageFromRequest())
            {
                try
                {
                    using (var archive = new ZipArchive(packageStream, ZipArchiveMode.Read, leaveOpen: true))
                    {
                        var reference = DateTime.UtcNow.AddDays(1); // allow "some" clock skew

                        var entryInTheFuture = archive.Entries.FirstOrDefault(
                            e => e.LastWriteTime.UtcDateTime > reference);

                        if (entryInTheFuture != null)
                        {
                            return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, string.Format(
                                                                        CultureInfo.CurrentCulture,
                                                                        Strings.PackageEntryFromTheFuture,
                                                                        entryInTheFuture.Name)));
                        }
                    }

                    using (var packageToPush = new PackageArchiveReader(packageStream, leaveStreamOpen: false))
                    {
                        try
                        {
                            PackageService.EnsureValid(packageToPush);
                        }
                        catch (Exception ex)
                        {
                            ex.Log();

                            var message = Strings.FailedToReadUploadFile;
                            if (ex is InvalidPackageException || ex is InvalidDataException || ex is EntityException)
                            {
                                message = ex.Message;
                            }

                            return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, message));
                        }

                        NuspecReader nuspec;
                        var          errors = ManifestValidator.Validate(packageToPush.GetNuspec(), out nuspec).ToArray();
                        if (errors.Length > 0)
                        {
                            var errorsString = string.Join("', '", errors.Select(error => error.ErrorMessage));
                            return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, string.Format(
                                                                        CultureInfo.CurrentCulture,
                                                                        errors.Length > 1 ? Strings.UploadPackage_InvalidNuspecMultiple : Strings.UploadPackage_InvalidNuspec,
                                                                        errorsString)));
                        }

                        if (nuspec.GetMinClientVersion() > Constants.MaxSupportedMinClientVersion)
                        {
                            return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, string.Format(
                                                                        CultureInfo.CurrentCulture,
                                                                        Strings.UploadPackage_MinClientVersionOutOfRange,
                                                                        nuspec.GetMinClientVersion())));
                        }

                        // Ensure that the user can push packages for this partialId.
                        var packageRegistration = PackageService.FindPackageRegistrationById(nuspec.GetId());
                        if (packageRegistration == null)
                        {
                            // Check if API key allows pushing a new package id
                            if (!ApiKeyScopeAllows(
                                    subject: nuspec.GetId(),
                                    requestedActions: NuGetScopes.PackagePush))
                            {
                                // User cannot push a new package ID as the API key scope does not allow it
                                return(new HttpStatusCodeWithBodyResult(HttpStatusCode.Unauthorized, Strings.ApiKeyNotAuthorized));
                            }
                        }
                        else
                        {
                            // Is the user allowed to push this Id?
                            if (!packageRegistration.IsOwner(user))
                            {
                                // Audit that a non-owner tried to push the package
                                await AuditingService.SaveAuditRecord(
                                    new FailedAuthenticatedOperationAuditRecord(
                                        user.Username,
                                        AuditedAuthenticatedOperationAction.PackagePushAttemptByNonOwner,
                                        attemptedPackage : new AuditedPackageIdentifier(
                                            nuspec.GetId(), nuspec.GetVersion().ToNormalizedStringSafe())));

                                // User cannot push a package to an ID owned by another user.
                                return(new HttpStatusCodeWithBodyResult(HttpStatusCode.Conflict,
                                                                        string.Format(CultureInfo.CurrentCulture, Strings.PackageIdNotAvailable,
                                                                                      nuspec.GetId())));
                            }

                            // Check if API key allows pushing the current package id
                            if (!ApiKeyScopeAllows(
                                    packageRegistration.Id,
                                    NuGetScopes.PackagePushVersion, NuGetScopes.PackagePush))
                            {
                                // User cannot push a package as the API key scope does not allow it
                                return(new HttpStatusCodeWithBodyResult(HttpStatusCode.Unauthorized, Strings.ApiKeyNotAuthorized));
                            }

                            // Check if a particular Id-Version combination already exists. We eventually need to remove this check.
                            string normalizedVersion = nuspec.GetVersion().ToNormalizedString();
                            bool   packageExists     =
                                packageRegistration.Packages.Any(
                                    p => string.Equals(
                                        p.NormalizedVersion,
                                        normalizedVersion,
                                        StringComparison.OrdinalIgnoreCase));

                            if (packageExists)
                            {
                                return(new HttpStatusCodeWithBodyResult(
                                           HttpStatusCode.Conflict,
                                           string.Format(CultureInfo.CurrentCulture, Strings.PackageExistsAndCannotBeModified,
                                                         nuspec.GetId(), nuspec.GetVersion().ToNormalizedStringSafe())));
                            }
                        }

                        var packageStreamMetadata = new PackageStreamMetadata
                        {
                            HashAlgorithm = Constants.Sha512HashAlgorithmId,
                            Hash          = CryptographyService.GenerateHash(packageStream.AsSeekableStream()),
                            Size          = packageStream.Length
                        };

                        var package = await PackageService.CreatePackageAsync(
                            packageToPush,
                            packageStreamMetadata,
                            user,
                            commitChanges : false);

                        await AutoCuratePackage.ExecuteAsync(package, packageToPush, commitChanges : false);

                        using (Stream uploadStream = packageStream)
                        {
                            uploadStream.Position = 0;

                            try
                            {
                                await PackageFileService.SavePackageFileAsync(package, uploadStream.AsSeekableStream());
                            }
                            catch (InvalidOperationException ex)
                            {
                                ex.Log();

                                return(new HttpStatusCodeWithBodyResult(HttpStatusCode.Conflict, Strings.UploadPackage_IdVersionConflict));
                            }
                        }

                        try
                        {
                            await EntitiesContext.SaveChangesAsync();
                        }
                        catch
                        {
                            // If saving to the DB fails for any reason, we need to delete the package we just saved.
                            await PackageFileService.DeletePackageFileAsync(nuspec.GetId(), nuspec.GetVersion().ToNormalizedString());

                            throw;
                        }

                        IndexingService.UpdatePackage(package);

                        // Write an audit record
                        await AuditingService.SaveAuditRecord(
                            new PackageAuditRecord(package, AuditedPackageAction.Create, PackageCreatedVia.Api));

                        // Notify user of push
                        MessageService.SendPackageAddedNotice(package,
                                                              Url.Action("DisplayPackage", "Packages", routeValues: new { id = package.PackageRegistration.Id, version = package.Version }, protocol: Request.Url.Scheme),
                                                              Url.Action("ReportMyPackage", "Packages", routeValues: new { id = package.PackageRegistration.Id, version = package.Version }, protocol: Request.Url.Scheme),
                                                              Url.Action("Account", "Users", routeValues: null, protocol: Request.Url.Scheme));

                        return(new HttpStatusCodeResult(HttpStatusCode.Created));
                    }
                }
                catch (InvalidPackageException ex)
                {
                    return(BadRequestForExceptionMessage(ex));
                }
                catch (InvalidDataException ex)
                {
                    return(BadRequestForExceptionMessage(ex));
                }
                catch (EntityException ex)
                {
                    return(BadRequestForExceptionMessage(ex));
                }
                catch (FrameworkException ex)
                {
                    return(BadRequestForExceptionMessage(ex));
                }
            }
        }
Ejemplo n.º 9
0
        public async Task <MembershipRequest> AddMembershipRequestAsync(Organization organization, string memberName, bool isAdmin)
        {
            organization = organization ?? throw new ArgumentNullException(nameof(organization));

            var membership = FindMembershipByUsername(organization, memberName);

            if (membership != null)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_AlreadyAMember, memberName));
            }

            var request = FindMembershipRequestByUsername(organization, memberName);

            if (request != null)
            {
                // If there is already an existing request, return it.
                // If the existing request grants collaborator but we are trying to create a request that grants admin, update the request to grant admin.
                request.IsAdmin = isAdmin || request.IsAdmin;
                await EntitiesContext.SaveChangesAsync();

                return(request);
            }

            if (Regex.IsMatch(memberName, GalleryConstants.EmailValidationRegex, RegexOptions.None, GalleryConstants.EmailValidationRegexTimeout))
            {
                throw new EntityException(Strings.AddMember_NameIsEmail);
            }

            var member = FindByUsername(memberName);

            if (member == null)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_UserNotFound, memberName));
            }

            if (!member.Confirmed)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_UserNotConfirmed, memberName));
            }

            if (member is Organization)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_UserIsOrganization, memberName));
            }

            // Ensure that the new member meets the AAD tenant policy for this organization.
            var policyResult = await SecurityPolicyService.EvaluateOrganizationPoliciesAsync(
                SecurityPolicyAction.JoinOrganization, organization, member);

            if (policyResult != SecurityPolicyResult.SuccessResult)
            {
                throw new EntityException(policyResult.ErrorMessage);
            }

            request = new MembershipRequest()
            {
                Organization      = organization,
                NewMember         = member,
                IsAdmin           = isAdmin,
                ConfirmationToken = Crypto.GenerateToken(),
                RequestDate       = DateTime.UtcNow,
            };
            organization.MemberRequests.Add(request);

            await EntitiesContext.SaveChangesAsync();

            return(request);
        }
Ejemplo n.º 10
0
        public async Task <Membership> AddMemberAsync(Organization organization, string memberName, string confirmationToken)
        {
            organization = organization ?? throw new ArgumentNullException(nameof(organization));

            var request = FindMembershipRequestByUsername(organization, memberName);

            if (request == null || request.ConfirmationToken != confirmationToken)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_MissingRequest, memberName));
            }

            var member = request.NewMember;

            organization.MemberRequests.Remove(request);

            if (!member.Confirmed)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_UserNotConfirmed, memberName));
            }

            if (member is Organization)
            {
                throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                        Strings.AddMember_UserIsOrganization, memberName));
            }

            var membership = FindMembershipByUsername(organization, memberName);

            if (membership == null)
            {
                // Ensure that the new member meets the AAD tenant policy for this organization.
                var policyResult = await SecurityPolicyService.EvaluateOrganizationPoliciesAsync(
                    SecurityPolicyAction.JoinOrganization, organization, member);

                if (policyResult != SecurityPolicyResult.SuccessResult)
                {
                    throw new EntityException(string.Format(CultureInfo.CurrentCulture,
                                                            Strings.AddMember_PolicyFailure, policyResult.ErrorMessage));
                }

                membership = new Membership()
                {
                    Member  = member,
                    IsAdmin = request.IsAdmin
                };
                organization.Members.Add(membership);

                await Auditing.SaveAuditRecordAsync(new UserAuditRecord(organization, AuditedUserAction.AddOrganizationMember, membership));
            }
            else
            {
                // If the user is already a member, update the existing membership.
                // If the request grants admin but this member is not an admin, grant admin to the member.
                membership.IsAdmin = membership.IsAdmin || request.IsAdmin;

                await Auditing.SaveAuditRecordAsync(new UserAuditRecord(organization, AuditedUserAction.UpdateOrganizationMember, membership));
            }

            await EntitiesContext.SaveChangesAsync();

            return(membership);
        }