Esempio n. 1
0
        /// <summary> Get the link sharing key for a project if it exists, otherwise create a new one. </summary>
        public async Task <string> GetLinkSharingKeyAsync(string projectId, string role)
        {
            SFProject project = await GetProjectAsync(projectId);

            if (!(project.CheckingConfig.ShareEnabled && project.CheckingConfig.ShareLevel == CheckingShareLevel.Anyone))
            {
                return(null);
            }
            SFProjectSecret projectSecret = await ProjectSecrets.GetAsync(projectId);

            // Link sharing keys have Email set to null and ExpirationTime set to null.
            string key = projectSecret.ShareKeys.SingleOrDefault(
                sk => sk.Email == null && sk.ProjectRole == role)?.Key;

            if (!string.IsNullOrEmpty(key))
            {
                return(key);
            }

            // Generate a new link sharing key for the given role
            key = _securityService.GenerateKey();
            await ProjectSecrets.UpdateAsync(p => p.Id == projectId,
                                             update => update.Add(p => p.ShareKeys,
                                                                  new ShareKey
            {
                Key            = key,
                ProjectRole    = role,
                ExpirationTime = null
            }
                                                                  ));

            return(key);
        }
Esempio n. 2
0
        /// <summary> Check that a share link is valid for a project and add the user to the project. </summary>
        public async Task CheckLinkSharingAsync(string curUserId, string projectId, string shareKey)
        {
            using (IConnection conn = await RealtimeService.ConnectAsync(curUserId))
            {
                IDocument <SFProject> projectDoc = await GetProjectDocAsync(projectId, conn);

                if (projectDoc.Data.UserRoles.ContainsKey(curUserId))
                {
                    return;
                }

                IDocument <User> userDoc = await conn.FetchAsync <User>(curUserId);

                string projectRole;
                // Attempt to get the role for the user from the Paratext registry
                Attempt <string> attempt = await TryGetProjectRoleAsync(projectDoc.Data, curUserId);

                if (!attempt.TryResult(out projectRole))
                {
                    // Get the project role that is specified in the sharekey
                    Attempt <SFProjectSecret> psAttempt = await ProjectSecrets.TryGetAsync(projectId);

                    if (psAttempt.TryResult(out SFProjectSecret ps))
                    {
                        projectRole = ps.ShareKeys.SingleOrDefault(sk => sk.Key == shareKey)?.ProjectRole;
                    }
                }
                // The share key was invalid
                if (projectRole == null)
                {
                    throw new ForbiddenException();
                }

                bool linkSharing = projectDoc.Data.CheckingConfig.ShareEnabled &&
                                   projectDoc.Data.CheckingConfig.ShareLevel == CheckingShareLevel.Anyone;
                if (linkSharing)
                {
                    // Add the user and remove the specific user share key if it exists. Link sharing keys
                    // have Email set to null and will not be removed.
                    await AddUserToProjectAsync(conn, projectDoc, userDoc, projectRole, true);

                    return;
                }
                // Look for a valid specific user share key.
                SFProjectSecret projectSecret = await ProjectSecrets.UpdateAsync(
                    p => p.Id == projectId && p.ShareKeys.Any(
                        sk => sk.Email != null && sk.Key == shareKey && sk.ExpirationTime > DateTime.UtcNow),
                    update => update.RemoveAll(p => p.ShareKeys, sk => sk.Key == shareKey)
                    );

                if (projectSecret != null)
                {
                    await AddUserToProjectAsync(conn, projectDoc, userDoc, projectRole, false);

                    return;
                }
                throw new ForbiddenException();
            }
        }
Esempio n. 3
0
        public async Task DeleteProjectAsync(string curUserId, string projectId)
        {
            string ptProjectId;

            using (IConnection conn = await RealtimeService.ConnectAsync(curUserId))
            {
                IDocument <SFProject> projectDoc = await conn.FetchAsync <SFProject>(projectId);

                if (!projectDoc.IsLoaded)
                {
                    throw new DataNotFoundException("The project does not exist.");
                }
                if (!IsProjectAdmin(projectDoc.Data, curUserId))
                {
                    throw new ForbiddenException();
                }

                ptProjectId = projectDoc.Data.ParatextId;
                // delete the project first, so that users get notified about the deletion
                string[] projectUserIds = projectDoc.Data.UserRoles.Keys.ToArray();
                await projectDoc.DeleteAsync();

                async Task removeUser(string projectUserId)
                {
                    IDocument <User> userDoc = await conn.FetchAsync <User>(projectUserId);

                    await RemoveUserFromProjectAsync(conn, projectDoc, userDoc);
                }

                var tasks = new List <Task>();
                foreach (string projectUserId in projectUserIds)
                {
                    tasks.Add(removeUser(projectUserId));
                }
                await Task.WhenAll(tasks);
            }

            await ProjectSecrets.DeleteAsync(projectId);

            await RealtimeService.DeleteProjectAsync(projectId);

            await _engineService.RemoveProjectAsync(projectId);

            string projectDir = Path.Combine(SiteOptions.Value.SiteDir, "sync", ptProjectId);

            if (FileSystemService.DirectoryExists(projectDir))
            {
                FileSystemService.DeleteDirectory(projectDir);
            }
            string audioDir = GetAudioDir(projectId);

            if (FileSystemService.DirectoryExists(audioDir))
            {
                FileSystemService.DeleteDirectory(audioDir);
            }
        }
Esempio n. 4
0
        protected virtual async Task AddUserToProjectAsync(IConnection conn, IDocument <TModel> projectDoc,
                                                           IDocument <User> userDoc, string projectRole, bool removeShareKeys = true)
        {
            await projectDoc.SubmitJson0OpAsync(op => op.Set(p => p.UserRoles[userDoc.Id], projectRole));

            if (removeShareKeys)
            {
                await ProjectSecrets.UpdateAsync(p => p.Id == projectDoc.Id,
                                                 update => update.RemoveAll(p => p.ShareKeys, sk => sk.Email == userDoc.Data.Email));
            }
            string siteId = SiteOptions.Value.Id;
            await userDoc.SubmitJson0OpAsync(op => op.Add(u => u.Sites[siteId].Projects, projectDoc.Id));
        }
Esempio n. 5
0
        /// <summary>Return list of email addresses with outstanding invitations</summary>
        public async Task <string[]> InvitedUsersAsync(string curUserId, string projectId)
        {
            SFProject project = await GetProjectAsync(projectId);

            if (!IsProjectAdmin(project, curUserId))
            {
                throw new ForbiddenException();
            }

            SFProjectSecret projectSecret = await ProjectSecrets.GetAsync(projectId);

            return(projectSecret.ShareKeys.Select(sk => sk.Email).ToArray());
        }
Esempio n. 6
0
        /// <summary>Is there already a pending invitation to the project for the specified email address?</summary>
        public async Task <bool> IsAlreadyInvitedAsync(string curUserId, string projectId, string email)
        {
            SFProject project = await GetProjectAsync(projectId);

            if (!IsProjectAdmin(project, curUserId) && !project.CheckingConfig.ShareEnabled)
            {
                throw new ForbiddenException();
            }

            if (email == null)
            {
                return(false);
            }
            return(await ProjectSecrets.Query()
                   .AnyAsync(p => p.Id == projectId && p.ShareKeys.Any(sk => sk.Email == email)));
        }
Esempio n. 7
0
        public async Task <bool> InviteAsync(string curUserId, string projectId, string email)
        {
            SFProject project = await GetProjectAsync(projectId);

            if (await RealtimeService.QuerySnapshots <User>()
                .AnyAsync(u => project.UserRoles.Keys.Contains(u.Id) && u.Email == email))
            {
                return(false);
            }
            SiteOptions siteOptions = SiteOptions.Value;

            if (!project.CheckingConfig.ShareEnabled && !IsProjectAdmin(project, curUserId))
            {
                throw new ForbiddenException();
            }

            // Invite a specific person. Reuse prior code, if any.
            SFProjectSecret projectSecret = await ProjectSecrets.UpdateAsync(
                p => p.Id == projectId && !p.ShareKeys.Any(sk => sk.Email == email),
                update => update.Add(p => p.ShareKeys,
                                     new ShareKey {
                Email = email, Key = _securityService.GenerateKey()
            }));

            if (projectSecret == null)
            {
                projectSecret = await ProjectSecrets.GetAsync(projectId);
            }
            string key = projectSecret.ShareKeys.Single(sk => sk.Email == email).Key;
            string url = $"{siteOptions.Origin}projects/{projectId}?sharing=true&shareKey={key}";
            string emailSpecificLinkMessage = _localizer[SharedResource.Keys.InviteLinkSharingOff];

            User inviter = await RealtimeService.GetSnapshotAsync <User>(curUserId);

            string subject      = _localizer[SharedResource.Keys.InviteSubject, project.Name, siteOptions.Name];
            var    greeting     = $"<p>{_localizer[SharedResource.Keys.InviteGreeting, "<p>", inviter.Name, project.Name, siteOptions.Name, $"<a href=\"{url}\">{url}</a><p>"]}";
            var    instructions = $"<p>{_localizer[SharedResource.Keys.InviteInstructions, siteOptions.Name, "<b>", "</b>"]}";
            var    pt           = $"<ul><li>{_localizer[SharedResource.Keys.InvitePTOption, "<b>", "</b>", siteOptions.Name]}</li>";
            var    google       = $"<li>{_localizer[SharedResource.Keys.InviteGoogleOption, "<b>", "</b>", siteOptions.Name]}</li>";
            var    facebook     = $"<li>{_localizer[SharedResource.Keys.InviteFacebookOption, "<b>", "</b>", siteOptions.Name]}</li>";
            var    withemail    = $"<li>{_localizer[SharedResource.Keys.InviteEmailOption, siteOptions.Name]}</li></ul></p><p></p>";
            var    signoff      = $"<p>{_localizer[SharedResource.Keys.InviteSignature, "<p>", siteOptions.Name]}</p>";
            var    emailBody    = $"{greeting}{emailSpecificLinkMessage}{instructions}{pt}{google}{facebook}{withemail}{signoff}";
            await _emailService.SendEmailAsync(email, subject, emailBody);

            return(true);
        }
Esempio n. 8
0
        /// <summary>Return list of email addresses with outstanding invitations</summary>
        public async Task <IReadOnlyList <InviteeStatus> > InvitedUsersAsync(string curUserId, string projectId)
        {
            SFProject project = await GetProjectAsync(projectId);

            if (!IsProjectAdmin(project, curUserId))
            {
                throw new ForbiddenException();
            }

            SFProjectSecret projectSecret = await ProjectSecrets.GetAsync(projectId);

            DateTime now = DateTime.UtcNow;

            return(projectSecret.ShareKeys.Where(s => s.Email != null).Select(sk =>
                                                                              new InviteeStatus {
                Email = sk.Email, Expired = sk.ExpirationTime < now
            }).ToArray());
        }
Esempio n. 9
0
        public async Task CheckLinkSharingAsync(string curUserId, string projectId, string shareKey = null)
        {
            using (IConnection conn = await RealtimeService.ConnectAsync(curUserId))
            {
                IDocument <SFProject> projectDoc = await GetProjectDocAsync(projectId, conn);

                if (projectDoc.Data.UserRoles.ContainsKey(curUserId))
                {
                    return;
                }

                IDocument <User> userDoc = await conn.FetchAsync <User>(curUserId);

                Attempt <string> attempt = await TryGetProjectRoleAsync(projectDoc.Data, curUserId);

                string projectRole = attempt.Result;
                if (shareKey != null)
                {
                    string          currentUserEmail = userDoc.Data.Email;
                    SFProjectSecret projectSecret    = await ProjectSecrets.UpdateAsync(
                        p => p.Id == projectId &&
                        p.ShareKeys.Any(sk => sk.Email == currentUserEmail && sk.Key == shareKey),
                        update => update.RemoveAll(p => p.ShareKeys, sk => sk.Email == currentUserEmail));

                    if (projectSecret != null)
                    {
                        await AddUserToProjectAsync(conn, projectDoc, userDoc, projectRole, false);

                        return;
                    }
                }
                if (projectDoc.Data.CheckingConfig.ShareEnabled == true &&
                    projectDoc.Data.CheckingConfig.ShareLevel == CheckingShareLevel.Anyone)
                {
                    // Users with the project link get added to the project. This also covers the case where
                    // a user was emailed a share key and the invite was cancelled, but link sharing is enabled
                    await AddUserToProjectAsync(conn, projectDoc, userDoc, projectRole);

                    return;
                }
                throw new ForbiddenException();
            }
        }
Esempio n. 10
0
        /// <summary>Cancel an outstanding project invitation.</summary>
        public async Task UninviteUserAsync(string curUserId, string projectId, string emailToUninvite)
        {
            SFProject project = await GetProjectAsync(projectId);

            if (!IsProjectAdmin(project, curUserId))
            {
                throw new ForbiddenException();
            }

            if (!await IsAlreadyInvitedAsync(curUserId, projectId, emailToUninvite))
            {
                // There is not an invitation for this email address
                return;
            }

            await ProjectSecrets.UpdateAsync(projectId, u =>
            {
                u.RemoveAll(secretSet => secretSet.ShareKeys, shareKey => shareKey.Email == (emailToUninvite));
            });
        }
Esempio n. 11
0
        /// <summary>
        /// Returns SF project id of created project.
        /// </summary>
        public async Task <string> CreateProjectAsync(string curUserId, SFProjectCreateSettings settings)
        {
            Attempt <UserSecret> userSecretAttempt = await _userSecrets.TryGetAsync(curUserId);

            if (!userSecretAttempt.TryResult(out UserSecret userSecret))
            {
                throw new DataNotFoundException("The user does not exist.");
            }

            IReadOnlyList <ParatextProject> ptProjects = await _paratextService.GetProjectsAsync(userSecret);

            ParatextProject ptProject = ptProjects.SingleOrDefault(p => p.ParatextId == settings.ParatextId);

            if (ptProject == null)
            {
                throw new DataNotFoundException("The paratext project does not exist.");
            }

            var project = new SFProject
            {
                ParatextId    = settings.ParatextId,
                Name          = ptProject.Name,
                ShortName     = ptProject.ShortName,
                WritingSystem = new WritingSystem {
                    Tag = ptProject.LanguageTag
                },
                TranslateConfig = new TranslateConfig
                {
                    TranslationSuggestionsEnabled = settings.TranslationSuggestionsEnabled
                },
                CheckingConfig = new CheckingConfig
                {
                    CheckingEnabled = settings.CheckingEnabled
                }
            };
            Attempt <string> attempt = await TryGetProjectRoleAsync(project, curUserId);

            if (!attempt.TryResult(out string projectRole) || projectRole != SFProjectRole.Administrator)
            {
                throw new ForbiddenException();
            }

            string projectId = ObjectId.GenerateNewId().ToString();

            using (IConnection conn = await RealtimeService.ConnectAsync(curUserId))
            {
                if (this.RealtimeService.QuerySnapshots <SFProject>().Any(
                        (SFProject sfProject) => sfProject.ParatextId == project.ParatextId))
                {
                    throw new InvalidOperationException(ErrorAlreadyConnectedKey);
                }
                IDocument <SFProject> projectDoc = await conn.CreateAsync <SFProject>(projectId, project);

                await ProjectSecrets.InsertAsync(new SFProjectSecret { Id = projectDoc.Id });

                IDocument <User> userDoc = await conn.FetchAsync <User>(curUserId);
                await AddUserToProjectAsync(conn, projectDoc, userDoc, SFProjectRole.Administrator, false);

                // Add the source after the project has been created
                // This will make the source project appear after the target, if it needs to be created
                if (settings.SourceParatextId != null && settings.SourceParatextId != settings.ParatextId)
                {
                    TranslateSource source = await this.GetTranslateSourceAsync(
                        curUserId, userSecret, settings.SourceParatextId, ptProjects);

                    await projectDoc.SubmitJson0OpAsync(op =>
                    {
                        UpdateSetting(op, p => p.TranslateConfig.Source, source);
                    });
                }

                if (projectDoc.Data.TranslateConfig.TranslationSuggestionsEnabled)
                {
                    var machineProject = new MachineProject
                    {
                        Id = projectDoc.Id,
                        SourceLanguageTag = projectDoc.Data.TranslateConfig.Source.WritingSystem.Tag,
                        TargetLanguageTag = projectDoc.Data.WritingSystem.Tag
                    };
                    await _engineService.AddProjectAsync(machineProject);
                }
            }

            await _syncService.SyncAsync(curUserId, projectId, true);

            return(projectId);
        }
Esempio n. 12
0
        public async Task <bool> InviteAsync(string curUserId, string projectId, string email, string locale,
                                             string role)
        {
            SFProject project = await GetProjectAsync(projectId);

            if (await RealtimeService.QuerySnapshots <User>()
                .AnyAsync(u => project.UserRoles.Keys.Contains(u.Id) && u.Email == email))
            {
                return(false);
            }
            SiteOptions siteOptions = SiteOptions.Value;

            if (!project.CheckingConfig.ShareEnabled && !IsProjectAdmin(project, curUserId))
            {
                throw new ForbiddenException();
            }
            CultureInfo.CurrentUICulture = new CultureInfo(locale);
            // Remove the user sharekey if expired
            await ProjectSecrets.UpdateAsync(
                p => p.Id == projectId,
                update => update.RemoveAll(p => p.ShareKeys,
                                           sk => sk.Email == email && sk.ExpirationTime < DateTime.UtcNow)
                );

            DateTime expTime = DateTime.UtcNow.AddDays(14);

            // Invite a specific person. Reuse prior code, if any.
            SFProjectSecret projectSecret = await ProjectSecrets.UpdateAsync(
                p => p.Id == projectId && !p.ShareKeys.Any(sk => sk.Email == email),
                update => update.Add(p => p.ShareKeys,
                                     new ShareKey
            {
                Email          = email,
                Key            = _securityService.GenerateKey(),
                ExpirationTime = expTime,
                ProjectRole    = role
            }
                                     )
                );

            if (projectSecret == null)
            {
                projectSecret = await ProjectSecrets.GetAsync(projectId);

                int index = projectSecret.ShareKeys.FindIndex(sk => sk.Email == email);

                // Renew the expiration time of the valid key
                await ProjectSecrets.UpdateAsync(
                    p => p.Id == projectId && p.ShareKeys.Any(sk => sk.Email == email),
                    update => update.Set(p => p.ShareKeys[index].ExpirationTime, expTime)
                    );
            }
            string key         = projectSecret.ShareKeys.Single(sk => sk.Email == email).Key;
            string url         = $"{siteOptions.Origin}projects/{projectId}?sharing=true&shareKey={key}&locale={locale}";
            string linkExpires = _localizer[SharedResource.Keys.InviteLinkExpires];

            User inviter = await RealtimeService.GetSnapshotAsync <User>(curUserId);

            string subject      = _localizer[SharedResource.Keys.InviteSubject, project.Name, siteOptions.Name];
            var    greeting     = $"<p>{_localizer[SharedResource.Keys.InviteGreeting, "<p>", inviter.Name, project.Name, siteOptions.Name, $"<a href=\"{url}\">{url}</a><p>"]}";
            var    instructions = $"<p>{_localizer[SharedResource.Keys.InviteInstructions, siteOptions.Name, "<b>", "</b>"]}";
            var    pt           = $"<ul><li>{_localizer[SharedResource.Keys.InvitePTOption, "<b>", "</b>", siteOptions.Name]}</li>";
            var    google       = $"<li>{_localizer[SharedResource.Keys.InviteGoogleOption, "<b>", "</b>", siteOptions.Name]}</li>";
            var    facebook     = $"<li>{_localizer[SharedResource.Keys.InviteFacebookOption, "<b>", "</b>", siteOptions.Name]}</li>";
            var    withemail    = $"<li>{_localizer[SharedResource.Keys.InviteEmailOption, siteOptions.Name]}</li></ul></p><p></p>";
            var    signoff      = $"<p>{_localizer[SharedResource.Keys.InviteSignature, "<p>", siteOptions.Name]}</p>";
            var    emailBody    = $"{greeting}{linkExpires}{instructions}{pt}{google}{facebook}{withemail}{signoff}";
            await _emailService.SendEmailAsync(email, subject, emailBody);

            return(true);
        }