Пример #1
0
    def post(self, request, project_slug):
        project = get_object_or_404(Project, slug=project_slug)

        if not AdminPermission.is_admin(request.user, project):
            return HttpResponseForbidden()

        version_slug = request.POST.get('version_slug')
        version = get_object_or_404(
            Version,
            project=project,
            slug=version_slug,
        )

        update_docs_task, build = trigger_build(project=project,
                                                version=version)
        if (update_docs_task, build) == (None, None):
            # Build was skipped
            messages.add_message(
                request,
                messages.WARNING,
                "This project is currently disabled and can't trigger new builds.",
            )
            return HttpResponseRedirect(
                reverse('builds_project_list', args=[project.slug]), )

        return HttpResponseRedirect(
            reverse('builds_detail', args=[project.slug, build.pk]), )
Пример #2
0
    def post(self, request, project_slug):
        project = get_object_or_404(Project, slug=project_slug)

        if not AdminPermission.is_admin(request.user, project):
            return HttpResponseForbidden()

        version_slug = request.POST.get('version_slug')
        version = get_object_or_404(
            Version,
            project=project,
            slug=version_slug,
        )

        update_docs_task, build = trigger_build(
            project=project,
            version=version,
        )
        if (update_docs_task, build) == (None, None):
            # Build was skipped
            messages.add_message(
                request,
                messages.WARNING,
                "This project is currently disabled and can't trigger new builds.",
            )
            return HttpResponseRedirect(
                reverse('builds_project_list', args=[project.slug]),
            )

        return HttpResponseRedirect(
            reverse('builds_detail', args=[project.slug, build.pk]),
        )
Пример #3
0
def serve_docs(request,
               project,
               subproject,
               lang_slug=None,
               version_slug=None,
               filename=''):
    """Exists to map existing proj, lang, version, filename views to the file format."""
    if not version_slug:
        version_slug = project.get_default_version()
    try:
        version = project.versions.public(request.user).get(slug=version_slug)
    except Version.DoesNotExist:
        # Properly raise a 404 if the version doesn't exist & a 401 if it does
        if project.versions.filter(slug=version_slug).exists():
            return _serve_401(request, project)
        raise Http404('Version does not exist.')
    filename = resolve_path(
        subproject or project,  # Resolve the subproject if it exists
        version_slug=version_slug,
        language=lang_slug,
        filename=filename,
        subdomain=
        True,  # subdomain will make it a "full" path without a URL prefix
    )
    if (version.privacy_level == constants.PRIVATE
            and not AdminPermission.is_member(user=request.user, obj=project)):
        return _serve_401(request, project)
    return _serve_symlink_docs(
        request,
        filename=filename,
        project=project,
        privacy_level=version.privacy_level,
    )
Пример #4
0
def serve_docs(
        request,
        project,
        subproject,
        lang_slug=None,
        version_slug=None,
        filename='',
):
    """Map existing proj, lang, version, filename views to the file format."""
    if not version_slug:
        version_slug = project.get_default_version()
    try:
        version = project.versions.public(request.user).get(slug=version_slug)
    except Version.DoesNotExist:
        # Properly raise a 404 if the version doesn't exist (or is inactive) and
        # a 401 if it does
        if project.versions.filter(slug=version_slug, active=True).exists():
            return _serve_401(request, project)
        raise Http404('Version does not exist.')
    filename = resolve_path(
        subproject or project,  # Resolve the subproject if it exists
        version_slug=version_slug,
        language=lang_slug,
        filename=filename,
        subdomain=True,  # subdomain will make it a "full" path without a URL prefix
    )
    if (version.privacy_level == constants.PRIVATE and
            not AdminPermission.is_member(user=request.user, obj=project)):
        return _serve_401(request, project)
    return _serve_symlink_docs(
        request,
        filename=filename,
        project=project,
        privacy_level=version.privacy_level,
    )
Пример #5
0
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        project = self.get_project()
        context['versions'] = self._get_versions(project)

        protocol = 'http'
        if self.request.is_secure():
            protocol = 'https'

        version_slug = project.get_default_version()

        context['badge_url'] = ProjectBadgeView.get_badge_url(
            project.slug,
            version_slug,
            protocol=protocol,
        )
        context['site_url'] = '{url}?badge={version}'.format(
            url=project.get_docs_url(version_slug),
            version=version_slug,
        )

        context['is_project_admin'] = AdminPermission.is_admin(
            self.request.user,
            project,
        )

        return context
Пример #6
0
 def _add_user_projects(self, queryset, user, admin=False, member=False):
     """Add projects from where `user` is an `admin` or a `member`."""
     projects = AdminPermission.projects(
         user=user,
         admin=admin,
         member=member,
     )
     return queryset | projects
Пример #7
0
    def test_project_admins_can_delete_subprojects_that_they_are_not_admin_of(self):
        self.project.users.add(self.user)
        self.assertFalse(AdminPermission.is_admin(self.user, self.subproject))

        response = self.client.post(
            '/dashboard/my-mainproject/subprojects/my-subproject/delete/')
        self.assertEqual(response.status_code, 302)
        self.assertTrue(self.subproject not in [r.child for r in self.project.subprojects.all()])
Пример #8
0
    def test_project_admins_can_delete_subprojects_that_they_are_not_admin_of(
            self):
        self.project.users.add(self.user)
        self.assertFalse(AdminPermission.is_admin(self.user, self.subproject))

        response = self.client.post(
            '/dashboard/my-mainproject/subprojects/my-subproject/delete/')
        self.assertEqual(response.status_code, 302)
        self.assertTrue(self.subproject not in
                        [r.child for r in self.project.subprojects.all()])
Пример #9
0
 def _add_from_user_projects(self, queryset, user):
     if user and user.is_authenticated:
         projects_pk = (AdminPermission.projects(
             user=user,
             admin=True,
             member=True,
         ).values_list('pk', flat=True))
         user_queryset = self.filter(build__project__in=projects_pk)
         queryset = user_queryset | queryset
     return queryset
Пример #10
0
    def get(self, request, *args, **kwargs):  # noqa
        """
        Process GET from link click and let user in to team.

        If user is already logged in, link the team member to that account. If
        the user is not logged in, and doesn't have an account, the user will be
        prompted to sign up.
        """
        # Linter doesn't like declaring `self.object` outside `__init__`.
        member = self.object = self.get_object()  # noqa
        if member is not None:
            if not request.user.is_authenticated:
                member.invite.count += 1
                member.invite.save()
                self.request.session.update({
                    'invite:allow_signup': True,
                    'invite:email': member.invite.email,
                    'invite': member.invite.pk,

                    # Auto-verify EmailAddress via django-allauth
                    'account_verified_email': member.invite.email,
                })
                url = reverse('account_signup')

                if AdminPermission.has_sso_enabled(member.team.organization):
                    url += f'?organization={member.team.organization.slug}'

                return HttpResponseRedirect(url)

            # If use is logged in, try to set the request user on the
            # fetched team member. If the member already exists on the team,
            # just delete the current member. Finally, get rid of the
            # invite too.
            org_slug = member.team.organization.slug
            invite = member.invite

            queryset = TeamMember.objects.filter(
                team=invite.team,
                member=self.request.user,
            )
            if queryset.exists():
                member.delete()
            else:
                member.member = self.request.user
                member.save()
            invite.delete()
            return HttpResponseRedirect(
                reverse(
                    'organization_detail',
                    kwargs={'slug': org_slug},
                ),
            )

        return HttpResponseRedirect(reverse('homepage'))
Пример #11
0
def project_versions(request, project_slug):
    """
    Project version list view.

    Shows the available versions and lets the user choose which ones to build.
    """
    max_inactive_versions = 100

    project = get_object_or_404(
        Project.objects.protected(request.user),
        slug=project_slug,
    )

    versions = Version.internal.public(
        user=request.user,
        project=project,
        only_active=False,
    )
    active_versions = versions.filter(active=True)

    # Limit inactive versions in case a project has a large number of branches or tags
    # Filter inactive versions based on the query string
    inactive_versions = versions.filter(active=False)
    version_filter = request.GET.get('version_filter', '')
    if version_filter:
        inactive_versions = inactive_versions.filter(
            verbose_name__icontains=version_filter)
    total_inactive_versions_count = inactive_versions.count()
    inactive_versions = inactive_versions[:max_inactive_versions]

    # If there's a wiped query string, check the string against the versions
    # list and display a success message. Deleting directories doesn't know how
    # to fail.  :)
    wiped = request.GET.get('wipe', '')
    wiped_version = versions.filter(slug=wiped)
    if wiped and wiped_version.exists():
        messages.success(request, 'Version wiped: ' + wiped)

    # Optimize project permission checks
    prefetch_related_objects([project], 'users')

    return render(
        request,
        'projects/project_version_list.html',
        {
            'inactive_versions': inactive_versions,
            'active_versions': active_versions,
            'project': project,
            'is_project_admin': AdminPermission.is_admin(
                request.user, project),
            'max_inactive_versions': max_inactive_versions,
            'total_inactive_versions_count': total_inactive_versions_count,
        },
    )
Пример #12
0
 def _add_from_user_projects(self, queryset, user):
     if user and user.is_authenticated:
         projects_pk = (AdminPermission.projects(
             user=user,
             admin=True,
             member=True,
         ).values_list('pk', flat=True))
         kwargs = {f'{self.project_field}__in': projects_pk}
         user_queryset = self.filter(**kwargs)
         queryset = user_queryset | queryset
     return queryset
Пример #13
0
 def _add_from_user_projects(self,
                             queryset,
                             user,
                             admin=False,
                             member=False):
     """Add related objects from projects where `user` is an `admin` or a `member`."""
     if user and user.is_authenticated:
         projects_pk = (AdminPermission.projects(
             user=user,
             admin=admin,
             member=member,
         ).values_list('pk', flat=True))
         user_queryset = self.filter(project__in=projects_pk)
         queryset = user_queryset | queryset
     return queryset
Пример #14
0
    def post(self, request, project_slug):
        project = get_object_or_404(Project, slug=project_slug)

        if not AdminPermission.is_admin(request.user, project):
            return HttpResponseForbidden()

        version_slug = request.POST.get('version_slug')
        version = get_object_or_404(
            Version,
            project=project,
            slug=version_slug,
        )

        trigger_build(project=project, version=version)
        return HttpResponseRedirect(reverse('builds_project_list', args=[project.slug]))
Пример #15
0
    def post(self, request, project_slug):
        project = get_object_or_404(Project, slug=project_slug)

        if not AdminPermission.is_admin(request.user, project):
            return HttpResponseForbidden()

        version_slug = request.POST.get('version_slug')
        version = get_object_or_404(
            Version,
            project=project,
            slug=version_slug,
        )

        _, build = trigger_build(project=project, version=version)
        return HttpResponseRedirect(
            reverse('builds_detail', args=[project.slug, build.pk]), )
Пример #16
0
    def notify_project_users(cls, projects):
        """
        Notify project users of deprecated view.

        :param projects: List of project instances
        :type projects: [:py:class:`Project`]
        """
        for project in projects:
            # Send one notification to each admin of the project
            for user in AdminPermission.admins(project):
                notification = cls(
                    context_object=project,
                    request=HttpRequest(),
                    user=user,
                )
                notification.send()
Пример #17
0
def sync_remote_repositories_organizations(organization_slugs=None):
    """
    Re-sync users member of organizations.

    It will trigger one `sync_remote_repositories` task per user.

    :param organization_slugs: list containg organization's slugs to sync. If
    not passed, all organizations with ALLAUTH SSO enabled will be synced

    :type organization_slugs: list
    """
    if organization_slugs:
        query = Organization.objects.filter(slug__in=organization_slugs)
        log.info(
            'Triggering SSO re-sync for organizations.',
            organization_slugs=organization_slugs,
            count=query.count(),
        )
    else:
        organization_ids = (SSOIntegration.objects.filter(
            provider=SSOIntegration.PROVIDER_ALLAUTH).values_list(
                'organization', flat=True))
        query = Organization.objects.filter(id__in=organization_ids)
        log.info(
            'Triggering SSO re-sync for all organizations.',
            count=query.count(),
        )

    n_task = -1
    for organization in query:
        members = AdminPermission.members(organization)
        log.info(
            'Triggering SSO re-sync for organization.',
            organization_slug=organization.slug,
            count=members.count(),
        )
        for user in members:
            n_task += 1
            sync_remote_repositories.apply_async(
                args=[user.pk],
                # delay the task by 0, 5, 10, 15, ... seconds
                countdown=n_task * 5,
            )
Пример #18
0
    def resync_sso_user_accounts(self, request, queryset):  # pylint: disable=no-self-use
        users_count = 0
        organizations_count = queryset.count()

        for ssointegration in queryset.select_related('organization'):
            members = AdminPermission.members(ssointegration.organization)
            log.info(
                'Triggering SSO re-sync for organization.',
                organization_slug=ssointegration.organization.slug,
                count=members.count(),
            )
            users_count += members.count()
            for user in members:
                sync_remote_repositories.delay(user.pk)

        messages.add_message(
            request, messages.INFO,
            f'Triggered resync for {organizations_count} organizations and {users_count} users.'
        )
Пример #19
0
    def clean_subproject(self):
        """Normalize subproject field

        Does lookup on against :py:class:`Project` to ensure matching project
        exists. Return the :py:class:`Project` object instead.
        """
        subproject_name = self.cleaned_data['subproject']
        subproject_qs = Project.objects.filter(slug=subproject_name)
        if not subproject_qs.exists():
            raise forms.ValidationError(
                (_("Project %(name)s does not exist") % {
                    'name': subproject_name
                }))
        subproject = subproject_qs.first()
        if not AdminPermission.is_admin(self.user, subproject):
            raise forms.ValidationError(
                _('You need to be admin of {name} in order to add it as '
                  'a subproject.'.format(name=subproject_name)))
        return subproject
Пример #20
0
 def get_token_for_project(cls, project, force_local=False):
     """Get access token for project by iterating over project users."""
     # TODO why does this only target GitHub?
     if not settings.ALLOW_PRIVATE_REPOS:
         return None
     token = None
     try:
         if settings.DONT_HIT_DB and not force_local:
             token = api.project(project.pk).token().get()['token']
         else:
             for user in AdminPermission.admins(project):
                 tokens = SocialToken.objects.filter(
                     account__user=user,
                     app__provider=cls.adapter.provider_id,
                 )
                 if tokens.exists():
                     token = tokens[0].token
     except Exception:
         log.exception('Failed to get token for project')
     return token
Пример #21
0
    def post(self, request, project_slug, build_pk):
        project = get_object_or_404(Project, slug=project_slug)
        build = get_object_or_404(Build, pk=build_pk)

        if not AdminPermission.is_admin(request.user, project):
            return HttpResponseForbidden()

        # NOTE: `terminate=True` is required for the child to attend our call
        # immediately when it's running the build. Otherwise, it finishes the
        # task. However, to revoke a task that has not started yet, we don't
        # need it.
        if build.state == BUILD_STATE_TRIGGERED:
            # Since the task won't be executed at all, we need to update the
            # Build object here.
            terminate = False
            build.state = BUILD_STATE_FINISHED
            build.success = False
            build.error = BuildCancelled.message
            build.length = 0
            build.save()
        else:
            # In this case, we left the update of the Build object to the task
            # itself to be executed in the `on_failure` handler.
            terminate = True

        log.warning(
            'Canceling build.',
            project_slug=project.slug,
            version_slug=build.version.slug,
            build_id=build.pk,
            build_task_id=build.task_id,
            terminate=terminate,
        )
        app.control.revoke(build.task_id,
                           signal=signal.SIGINT,
                           terminate=terminate)

        return HttpResponseRedirect(
            reverse('builds_detail', args=[project.slug, build.pk]), )
Пример #22
0
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        project = self.get_project()

        # Get filtered and sorted versions
        versions = self._get_versions(project)
        if settings.RTD_EXT_THEME_ENABLED:
            filter = ProjectVersionListFilterSet(
                self.request.GET,
                queryset=versions,
            )
            context['filter'] = filter
            versions = filter.qs
        context['versions'] = versions

        protocol = 'http'
        if self.request.is_secure():
            protocol = 'https'

        version_slug = project.get_default_version()

        context['badge_url'] = ProjectBadgeView.get_badge_url(
            project.slug,
            version_slug,
            protocol=protocol,
        )
        context['site_url'] = '{url}?badge={version}'.format(
            url=project.get_docs_url(version_slug),
            version=version_slug,
        )

        context['is_project_admin'] = AdminPermission.is_admin(
            self.request.user,
            project,
        )

        return context
Пример #23
0
def is_admin(user, project):
    return AdminPermission.is_admin(user, project)
Пример #24
0
 def members(self):
     return AdminPermission.members(self)
Пример #25
0
def has_sso_enabled_filter(obj, provider=None):
    """Check if `obj` has sso enabled for `provider`."""
    return AdminPermission.has_sso_enabled(obj, provider)
Пример #26
0
def is_member(user, project):
    return AdminPermission.is_member(user, project)
Пример #27
0
 def has_object_permission(self, request, view, obj):
     if request.method in permissions.SAFE_METHODS:
         return True  # TODO: Similar logic to #1084
     return AdminPermission.is_admin(request.user, obj.node.project)
Пример #28
0
    def handle(self, *args, **options):
        if not options['email'] and not options['notification']:
            print("--email or --notification is required.")
            sys.exit(1)

        project = options['project']
        organization = options['organization']
        if project and organization:
            print("--project and --organization can\'t be used together.")
            sys.exit(1)

        if project:
            project = Project.objects.get(slug=project)
            users = AdminPermission.owners(project)
        elif organization:
            organization = Organization.objects.get(slug=organization)
            users = AdminPermission.owners(organization)
        elif settings.RTD_ALLOW_ORGANIZATIONS:
            users = (User.objects.filter(
                organizationowner__organization__disabled=False).distinct())
        else:
            users = (User.objects.filter(projects__skip=False).distinct())

        print('len(owners)={} production={} email={} notification={}'.format(
            users.count(),
            bool(options['production']),
            options['email'],
            options['notification'],
        ))

        if input('Continue? y/n: ') != 'y':
            print('Aborting run.')
            return

        notification_content = ''
        if options['notification']:
            file = Path(options['notification'])
            with file.open() as f:
                notification_content = f.read()

        email_subject = ''
        email_content = ''
        if options['email']:
            file = Path(options['email'])
            with file.open() as f:
                content = f.read().split('\n')
            email_subject = content[0].strip()
            email_content = '\n'.join(content[1:]).strip()

        resp = contact_users(
            users=users,
            email_subject=email_subject,
            email_content=email_content,
            notification_content=notification_content,
            sticky_notification=options['sticky'],
            dryrun=not options['production'],
        )

        email = resp['email']
        total = len(email['sent'])
        total_failed = len(email['failed'])
        print(f'Emails sent ({total}):')
        pprint(email['sent'])
        print(f'Failed emails ({total_failed}):')
        pprint(email['failed'])

        notification = resp['notification']
        total = len(notification['sent'])
        total_failed = len(notification['failed'])
        print(f'Notifications sent ({total})')
        pprint(notification['sent'])
        print(f'Failed notifications ({total_failed})')
        pprint(notification['failed'])
Пример #29
0
def is_project_user(user, project):
    """Checks if the user has access to the project."""
    return user in AdminPermission.members(project)
Пример #30
0
def is_admin(user, project):
    return AdminPermission.is_admin(user, project)
Пример #31
0
 def has_object_permission(self, request, view, obj):
     if request.method in permissions.SAFE_METHODS:
         return True  # TODO: Similar logic to #1084
     return AdminPermission.is_admin(request.user, obj.node.project)
Пример #32
0
def send_build_status(build_pk, commit, status, link_to_build=False):
    """
    Send Build Status to Git Status API for project external versions.

    It tries using these services' account in order:

    1. user's account that imported the project
    2. each user's account from the project's maintainers

    :param build_pk: Build primary key
    :param commit: commit sha of the pull/merge request
    :param status: build status failed, pending, or success to be sent.
    """
    # TODO: Send build status for BitBucket.
    build = Build.objects.filter(pk=build_pk).first()
    if not build:
        return

    provider_name = build.project.git_provider_name

    log.info('Sending build status. build=%s, project=%s', build.pk,
             build.project.slug)

    if provider_name in [GITHUB_BRAND, GITLAB_BRAND]:
        # get the service class for the project e.g: GitHubService.
        service_class = build.project.git_service_class()
        users = build.project.users.all()

        try:
            remote_repository = build.project.remote_repository
            remote_repository_relations = (
                remote_repository.remote_repository_relations.filter(
                    account__isnull=False,
                    # Use ``user_in=`` instead of ``user__projects=`` here
                    # because User's are not related to Project's directly in
                    # Read the Docs for Business
                    user__in=AdminPermission.members(build.project),
                ).select_related('account', 'user').only('user', 'account'))

            # Try using any of the users' maintainer accounts
            # Try to loop through all remote repository relations for the projects users
            for relation in remote_repository_relations:
                service = service_class(relation.user, relation.account)
                # Send status report using the API.
                success = service.send_build_status(
                    build=build,
                    commit=commit,
                    state=status,
                    link_to_build=link_to_build,
                )

                if success:
                    log.info(
                        'Build status report sent correctly. '
                        'project=%s build=%s status=%s commit=%s user=%s',
                        build.project.slug,
                        build.pk,
                        status,
                        commit,
                        relation.user.username,
                    )
                    return True

        except RemoteRepository.DoesNotExist:
            log.warning(
                'Project does not have a RemoteRepository. project=%s',
                build.project.slug,
            )
            # Try to send build status for projects with no RemoteRepository
            for user in users:
                services = service_class.for_user(user)
                # Try to loop through services for users all social accounts
                # to send successful build status
                for service in services:
                    success = service.send_build_status(build, commit, status)
                    if success:
                        log.info(
                            'Build status report sent correctly using an user account. '
                            'project=%s build=%s status=%s commit=%s user=%s',
                            build.project.slug,
                            build.pk,
                            status,
                            commit,
                            user.username,
                        )
                        return True

        for user in users:
            # Send Site notification about Build status reporting failure
            # to all the users of the project.
            notification = GitBuildStatusFailureNotification(
                context_object=build.project,
                extra_context={'provider_name': provider_name},
                user=user,
                success=False,
            )
            notification.send()

        log.info('No social account or repository permission available for %s',
                 build.project.slug)
        return False
Пример #33
0
    def post(self, request, project_slug):
        commit_to_retrigger = None
        project = get_object_or_404(Project, slug=project_slug)

        if not AdminPermission.is_admin(request.user, project):
            return HttpResponseForbidden()

        version_slug = request.POST.get('version_slug')
        build_pk = request.POST.get('build_pk')

        if build_pk:
            # Filter over external versions only when re-triggering a specific build
            version = get_object_or_404(
                Version.external.public(self.request.user),
                slug=version_slug,
                project=project,
            )

            build_to_retrigger = get_object_or_404(
                Build.objects.all(),
                pk=build_pk,
                version=version,
            )
            if build_to_retrigger != Build.objects.filter(
                    version=version).first():
                messages.add_message(
                    request,
                    messages.ERROR,
                    "This build can't be re-triggered because it's "
                    "not the latest build for this version.",
                )
                return HttpResponseRedirect(request.path)

            # Set either the build to re-trigger it or None
            if build_to_retrigger:
                commit_to_retrigger = build_to_retrigger.commit
                log.info(
                    'Re-triggering build.',
                    project_slug=project.slug,
                    version_slug=version.slug,
                    build_commit=build_to_retrigger.commit,
                    build_id=build_to_retrigger.pk,
                )
        else:
            # Use generic query when triggering a normal build
            version = get_object_or_404(
                self._get_versions(project),
                slug=version_slug,
            )

        update_docs_task, build = trigger_build(
            project=project,
            version=version,
            commit=commit_to_retrigger,
        )
        if (update_docs_task, build) == (None, None):
            # Build was skipped
            messages.add_message(
                request,
                messages.WARNING,
                "This project is currently disabled and can't trigger new builds.",
            )
            return HttpResponseRedirect(
                reverse('builds_project_list', args=[project.slug]), )

        return HttpResponseRedirect(
            reverse('builds_detail', args=[project.slug, build.pk]), )