예제 #1
0
    def redirect_branch_or_repo(self):
        """Redirect /+code/<foo> to the branch or repository named 'foo'.

        'foo' can be the unique name of the branch/repo, or any of the aliases
        for the branch/repo.

        If 'foo' is invalid, or exists but the user does not have permission to
        view 'foo', a NotFoundError is raised, resulting in a 404 error.

        Unlike +branch, this works for both git and bzr repositories/branches.
        """
        target_url = None
        path = '/'.join(self.request.stepstogo)

        # Try a Git repository lookup first, since the schema is simpler and
        # so it's quicker.
        try:
            repository, trailing = getUtility(IGitLookup).getByPath(path)
            if repository is not None:
                target_url = canonical_url(repository)
                if trailing:
                    target_url = urlappend(target_url, trailing)
        except (InvalidNamespace, InvalidProductName, NameLookupFailed,
                Unauthorized):
            # Either the git repository wasn't found, or it was found but we
            # lack authority to access it. In either case, attempt a bzr lookup
            # so don't set target_url
            pass

        # Attempt a bzr lookup as well:
        try:
            branch, trailing = getUtility(IBranchLookup).getByLPPath(path)
            bzr_url = canonical_url(branch)
            if trailing != '':
                bzr_url = urlappend(bzr_url, trailing)

            if target_url and branch.product is not None:
                # Project has both a bzr branch and a git repo. There's no
                # right thing we can do here, so pretend we didn't see
                # anything at all.
                if branch.product.vcs is None:
                    target_url = None
                # if it's set to BZR, then set this branch as the target
                if branch.product.vcs == VCSType.BZR:
                    target_url = bzr_url
            else:
                target_url = bzr_url
        except (NoLinkedBranch, CannotHaveLinkedBranch, InvalidNamespace,
                InvalidProductName, NotFoundError):
            # No bzr branch found either.
            pass

        # Either neither bzr nor git returned matches, or they did but we're
        # not authorised to view them, or they both did and the project has not
        # set its 'vcs' property to indicate which one to prefer. In all cases
        # raise a 404:
        if not target_url:
            raise NotFoundError

        return self.redirectSubTree(target_url)
예제 #2
0
 def getExternalBugTrackerToUse(self):
     """See `IExternalBugTracker`."""
     base_auth_url = urlappend(self.baseurl, 'launchpad-auth')
     # Any token will do.
     auth_url = urlappend(base_auth_url, 'check')
     try:
         response = self.urlopen(auth_url)
     except urllib2.HTTPError as error:
         # If the error is HTTP 401 Unauthorized then we're
         # probably talking to the LP plugin.
         if error.code == 401:
             return TracLPPlugin(self.baseurl)
         else:
             return self
     except urllib2.URLError as error:
         return self
     else:
         # If the response contains a trac_auth cookie then we're
         # talking to the LP plugin. However, it's unlikely that
         # the remote system will authorize the bogus auth token we
         # sent, so this check is really intended to detect broken
         # Trac instances that return HTTP 200 for a missing page.
         for set_cookie in response.headers.getheaders('Set-Cookie'):
             cookie = SimpleCookie(set_cookie)
             if 'trac_auth' in cookie:
                 return TracLPPlugin(self.baseurl)
         else:
             return self
예제 #3
0
 def getExternalBugTrackerToUse(self):
     """See `IExternalBugTracker`."""
     base_auth_url = urlappend(self.baseurl, 'launchpad-auth')
     # Any token will do.
     auth_url = urlappend(base_auth_url, 'check')
     try:
         with override_timeout(config.checkwatches.default_socket_timeout):
             response = urlfetch(auth_url, use_proxy=True)
     except requests.HTTPError as e:
         # If the error is HTTP 401 Unauthorized then we're
         # probably talking to the LP plugin.
         if e.response.status_code == 401:
             return TracLPPlugin(self.baseurl)
         else:
             return self
     except requests.RequestException:
         return self
     else:
         # If the response contains a trac_auth cookie then we're
         # talking to the LP plugin. However, it's unlikely that
         # the remote system will authorize the bogus auth token we
         # sent, so this check is really intended to detect broken
         # Trac instances that return HTTP 200 for a missing page.
         for cookie in response.cookies:
             if cookie.name == 'trac_auth':
                 return TracLPPlugin(self.baseurl)
         else:
             return self
예제 #4
0
 def getExternalBugTrackerToUse(self):
     """See `IExternalBugTracker`."""
     base_auth_url = urlappend(self.baseurl, 'launchpad-auth')
     # Any token will do.
     auth_url = urlappend(base_auth_url, 'check')
     try:
         response = self.urlopen(auth_url)
     except urllib2.HTTPError as error:
         # If the error is HTTP 401 Unauthorized then we're
         # probably talking to the LP plugin.
         if error.code == 401:
             return TracLPPlugin(self.baseurl)
         else:
             return self
     except urllib2.URLError as error:
         return self
     else:
         # If the response contains a trac_auth cookie then we're
         # talking to the LP plugin. However, it's unlikely that
         # the remote system will authorize the bogus auth token we
         # sent, so this check is really intended to detect broken
         # Trac instances that return HTTP 200 for a missing page.
         for set_cookie in response.headers.getheaders('Set-Cookie'):
             cookie = SimpleCookie(set_cookie)
             if 'trac_auth' in cookie:
                 return TracLPPlugin(self.baseurl)
         else:
             return self
예제 #5
0
    def _authenticate(self):
        """Authenticate with the Trac instance."""
        token_text = self._generateAuthenticationToken()
        base_auth_url = urlappend(self.baseurl, 'launchpad-auth')
        auth_url = urlappend(base_auth_url, token_text)

        try:
            self._fetchPage(auth_url)
        except BugTrackerConnectError as e:
            raise BugTrackerAuthenticationError(self.baseurl, e.error)
예제 #6
0
    def _authenticate(self):
        """Authenticate with the Trac instance."""
        token_text = self._generateAuthenticationToken()
        base_auth_url = urlappend(self.baseurl, 'launchpad-auth')
        auth_url = urlappend(base_auth_url, token_text)

        try:
            self._getPage(auth_url)
        except BugTrackerConnectError as e:
            raise BugTrackerAuthenticationError(self.baseurl, e.error)
    def traverse(self, path, first_segment, use_default_referer=True):
        """Traverse to 'path' using a 'LaunchpadRootNavigation' object.

        Using the Zope traversal machinery, traverse to the path given by
        'segments', starting at a `LaunchpadRootNavigation` object.

        CAUTION: Prefer test_traverse to this method, because it correctly
        establishes the global request.

        :param path: A slash-delimited path.
        :param use_default_referer: If True, set the referer attribute in the
            request header to DEFAULT_REFERER = "http://launchpad.dev"
            (otherwise it remains as None)
        :return: The object found.
        """
        # XXX: What's the difference between first_segment and path? -- mbp
        # 2011-06-27.
        extra = {'PATH_INFO': urlappend('/%s' % first_segment, path)}
        if use_default_referer:
            extra['HTTP_REFERER'] = DEFAULT_REFERER
        request = LaunchpadTestRequest(**extra)
        segments = reversed(path.split('/'))
        request.setTraversalStack(segments)
        traverser = LaunchpadRootNavigation(
            getUtility(ILaunchpadRoot), request=request)
        return traverser.publishTraverse(request, first_segment)
예제 #8
0
    def href(self):
        """The location of the feed.

        E.g.  http://feeds.launchpad.net/firefox/revisions.atom
        """
        return urlappend(canonical_url(self.context, rootsite='feeds'),
                         'revisions.atom')
예제 #9
0
    def traverse(self, path, first_segment, use_default_referer=True):
        """Traverse to 'path' using a 'LaunchpadRootNavigation' object.

        Using the Zope traversal machinery, traverse to the path given by
        'segments', starting at a `LaunchpadRootNavigation` object.

        CAUTION: Prefer test_traverse to this method, because it correctly
        establishes the global request.

        :param path: A slash-delimited path.
        :param use_default_referer: If True, set the referer attribute in the
            request header to DEFAULT_REFERER = "http://launchpad.dev"
            (otherwise it remains as None)
        :return: The object found.
        """
        # XXX: What's the difference between first_segment and path? -- mbp
        # 2011-06-27.
        extra = {'PATH_INFO': urlappend('/%s' % first_segment, path)}
        if use_default_referer:
            extra['HTTP_REFERER'] = DEFAULT_REFERER
        request = LaunchpadTestRequest(**extra)
        segments = reversed(path.split('/'))
        request.setTraversalStack(segments)
        traverser = LaunchpadRootNavigation(getUtility(ILaunchpadRoot),
                                            request=request)
        return traverser.publishTraverse(request, first_segment)
예제 #10
0
def get_server_url():
    """Return the URL for this server's OpenID endpoint.

    This is wrapped in a function (instead of a constant) to make sure the
    vhost.testopenid section is not required in production configs.
    """
    return urlappend(allvhosts.configs['testopenid'].rooturl, '+openid')
예제 #11
0
 def _uploadFile(cls, lfa, lfc):
     """Upload a single file."""
     assert config.snappy.store_upload_url is not None
     unscanned_upload_url = urlappend(
         config.snappy.store_upload_url, "unscanned-upload/")
     lfa.open()
     try:
         lfa_wrapper = LibraryFileAliasWrapper(lfa)
         encoder = MultipartEncoder(
             fields={
                 "binary": (
                     lfa.filename, lfa_wrapper, "application/octet-stream"),
                 })
         # XXX cjwatson 2016-05-09: This should add timeline information,
         # but that's currently difficult in jobs.
         try:
             response = urlfetch(
                 unscanned_upload_url, method="POST", data=encoder,
                 headers={
                     "Content-Type": encoder.content_type,
                     "Accept": "application/json",
                     })
             response_data = response.json()
             if not response_data.get("successful", False):
                 raise UploadFailedResponse(response.text)
             return {"upload_id": response_data["upload_id"]}
         except requests.HTTPError as e:
             raise cls._makeSnapStoreError(UploadFailedResponse, e)
     finally:
         lfa.close()
예제 #12
0
 def getRedirectURL(self, current_url, query_string):
     """Get the URL to redirect to.
     :param current_url: The URL of the current page.
     :param query_string: The string that should be appended to the current
         url.
     """
     return urlappend(current_url, '+login' + query_string)
예제 #13
0
 def getRedirectURL(self, current_url, query_string):
     """Get the URL to redirect to.
     :param current_url: The URL of the current page.
     :param query_string: The string that should be appended to the current
         url.
     """
     return urlappend(current_url, '+login' + query_string)
예제 #14
0
 def _uploadApp(cls, snap, upload_data):
     """Create a new store upload based on the uploaded file."""
     assert config.snappy.store_url is not None
     assert snap.store_name is not None
     upload_url = urlappend(config.snappy.store_url, "dev/api/snap-push/")
     data = {
         "name": snap.store_name,
         "updown_id": upload_data["upload_id"],
         "series": snap.store_series.name,
         }
     # XXX cjwatson 2016-05-09: This should add timeline information, but
     # that's currently difficult in jobs.
     try:
         assert snap.store_secrets is not None
         response = urlfetch(
             upload_url, method="POST", json=data,
             auth=MacaroonAuth(
                 snap.store_secrets["root"],
                 snap.store_secrets.get("discharge")))
         response_data = response.json()
         return response_data["status_details_url"]
     except requests.HTTPError as e:
         if e.response.status_code == 401:
             if (e.response.headers.get("WWW-Authenticate") ==
                     "Macaroon needs_refresh=1"):
                 raise NeedsRefreshResponse()
             else:
                 raise cls._makeSnapStoreError(
                     UnauthorizedUploadResponse, e)
         raise cls._makeSnapStoreError(UploadFailedResponse, e)
예제 #15
0
    def href(self):
        """The location of the feed.

        E.g.  http://feeds.launchpad.net/firefox/revisions.atom
        """
        return urlappend(canonical_url(self.context, rootsite='feeds'),
                         'revisions.atom')
예제 #16
0
def get_server_url():
    """Return the URL for this server's OpenID endpoint.

    This is wrapped in a function (instead of a constant) to make sure the
    vhost.testopenid section is not required in production configs.
    """
    return urlappend(allvhosts.configs['testopenid'].rooturl, '+openid')
예제 #17
0
 def _release(cls, snap, revision):
     """Release a snap revision to specified channels."""
     release_url = urlappend(
         config.snappy.store_url, "dev/api/snap-release/")
     data = {
         "name": snap.store_name,
         "revision": revision,
         # The security proxy is useless and breaks JSON serialisation.
         "channels": removeSecurityProxy(snap.store_channels),
         "series": snap.store_series.name,
         }
     # XXX cjwatson 2016-06-28: This should add timeline information, but
     # that's currently difficult in jobs.
     try:
         assert snap.store_secrets is not None
         urlfetch(
             release_url, method="POST", json=data,
             auth=MacaroonAuth(
                 snap.store_secrets["root"],
                 snap.store_secrets.get("discharge")))
     except requests.HTTPError as e:
         if e.response.status_code == 401:
             if (e.response.headers.get("WWW-Authenticate") ==
                     "Macaroon needs_refresh=1"):
                 raise NeedsRefreshResponse()
         raise cls._makeSnapStoreError(ReleaseFailedResponse, e)
예제 #18
0
    def describeJobs(self, dsd):
        """Describe any jobs that may be pending for `dsd`.

        Shows "synchronizing..." if the entry is being synchronized,
        "updating..." if the DSD is being updated with package changes and
        "waiting in <queue>..." if the package is in the distroseries
        queues (<queue> will be NEW or UNAPPROVED and links to the
        relevant queue page).

        :param dsd: A `DistroSeriesDifference` on the page.
        :return: An HTML text describing work that is pending or in
            progress for `dsd`; or None.
        """
        has_pending_dsd_update = self.hasPendingDSDUpdate(dsd)
        pending_sync = self.pendingSync(dsd)
        if not has_pending_dsd_update and not pending_sync:
            return None

        description = []
        if has_pending_dsd_update:
            description.append("updating")
        if pending_sync is not None:
            # If the pending sync is waiting in the distroseries queues,
            # provide a handy link to there.
            queue_item = getUtility(IPackageUploadSet).getByPackageCopyJobIDs(
                (pending_sync.id, )).any()
            if queue_item is None:
                description.append("synchronizing")
            else:
                url = urlappend(
                    canonical_url(self.context),
                    "+queue?queue_state=%s" % queue_item.status.value)
                description.append('waiting in <a href="%s">%s</a>' %
                                   (url, queue_item.status.name))
        return " and ".join(description) + "&hellip;"
예제 #19
0
    def describeJobs(self, dsd):
        """Describe any jobs that may be pending for `dsd`.

        Shows "synchronizing..." if the entry is being synchronized,
        "updating..." if the DSD is being updated with package changes and
        "waiting in <queue>..." if the package is in the distroseries
        queues (<queue> will be NEW or UNAPPROVED and links to the
        relevant queue page).

        :param dsd: A `DistroSeriesDifference` on the page.
        :return: An HTML text describing work that is pending or in
            progress for `dsd`; or None.
        """
        has_pending_dsd_update = self.hasPendingDSDUpdate(dsd)
        pending_sync = self.pendingSync(dsd)
        if not has_pending_dsd_update and not pending_sync:
            return None

        description = []
        if has_pending_dsd_update:
            description.append("updating")
        if pending_sync is not None:
            # If the pending sync is waiting in the distroseries queues,
            # provide a handy link to there.
            queue_item = getUtility(IPackageUploadSet).getByPackageCopyJobIDs(
                (pending_sync.id,)).any()
            if queue_item is None:
                description.append("synchronizing")
            else:
                url = urlappend(
                    canonical_url(self.context), "+queue?queue_state=%s" %
                        queue_item.status.value)
                description.append('waiting in <a href="%s">%s</a>' %
                    (url, queue_item.status.name))
        return " and ".join(description) + "&hellip;"
예제 #20
0
 def _getTemplateParams(self, email, recipient):
     """See `BaseMailer`."""
     params = super(TeamMembershipMailer,
                    self)._getTemplateParams(email, recipient)
     params["recipient"] = recipient.displayname
     reason, _ = self._recipients.getReason(email)
     if reason.recipient_class is not None:
         params["recipient_class"] = reason.recipient_class
     params["member"] = self.member.unique_displayname
     params["membership_invitations_url"] = "%s/+invitation/%s" % (
         canonical_url(self.member), self.team.name)
     params["team"] = self.team.unique_displayname
     params["team_url"] = canonical_url(self.team)
     if self.membership is not None:
         params["membership_url"] = canonical_url(self.membership)
     if reason.recipient_class == "bulk" and self.reviewer == self.member:
         params["reviewer"] = "the user"
     elif self.reviewer is not None:
         params["reviewer"] = self.reviewer.unique_displayname
     if self.team.mailing_list is not None:
         template = get_email_template("team-list-subscribe-block.txt",
                                       app="registry")
         editemails_url = urlappend(
             canonical_url(getUtility(ILaunchpadRoot)),
             "~/+editmailinglists")
         list_instructions = template % {"editemails_url": editemails_url}
     else:
         list_instructions = ""
     params["list_instructions"] = list_instructions
     params.update(self.extra_params)
     return params
예제 #21
0
    def http_url(self):
        """Return the webapp URL for the context `LibraryFileAlias`.

        Preserve the `LibraryFileAlias.http_url` behaviour for deleted
        files, returning None.

        Mask webservice requests if it's the case, so the returned URL will
        be always relative to the parent webapp URL.
        """
        if self.context.deleted:
            return None

        parent_url = canonical_url(self.parent, request=self.request)
        traversal_url = urlappend(parent_url, '+files')
        url = urlappend(traversal_url,
                        url_path_quote(self.context.filename.encode('utf-8')))
        return url
예제 #22
0
 def assertRequest(self, url_suffix, json_data=None, method=None, **kwargs):
     [request] = self.requests
     self.assertThat(request, MatchesStructure.byEquality(
         url=urlappend(self.endpoint, url_suffix), method=method, **kwargs))
     if json_data is not None:
         self.assertEqual(json_data, json.loads(request.body))
     timeline = get_request_timeline(get_current_browser_request())
     action = timeline.actions[-1]
     self.assertEqual("git-hosting-%s" % method.lower(), action.category)
     self.assertEqual(
         "/" + url_suffix.split("?", 1)[0], action.detail.split(" ", 1)[0])
예제 #23
0
    def http_url(self):
        if self.context.deleted:
            return None

        url = canonical_url(self.parent.archive, request=self.request)
        return urlappend(
            url, '/'.join([
                '+sourcefiles', self.parent.source_package_name,
                self.parent.source_package_version,
                url_path_quote(self.context.filename.encode('utf-8'))
            ]))
예제 #24
0
    def render(self):
        # Reauthentication is called for by a query string parameter.
        reauth_qs = self.request.query_string_params.get('reauth', ['0'])
        do_reauth = int(reauth_qs[0])
        if self.account is not None and not do_reauth:
            return AlreadyLoggedInView(self.context, self.request)()

        # Allow unauthenticated users to have sessions for the OpenID
        # handshake to work.
        allowUnauthenticatedSession(self.request)
        consumer = self._getConsumer()
        openid_vhost = config.launchpad.openid_provider_vhost

        timeline_action = get_request_timeline(self.request).start(
            "openid-association-begin",
            allvhosts.configs[openid_vhost].rooturl,
            allow_nested=True)
        try:
            self.openid_request = consumer.begin(
                allvhosts.configs[openid_vhost].rooturl)
        finally:
            timeline_action.finish()
        self.openid_request.addExtension(
            sreg.SRegRequest(required=['email', 'fullname']))

        # Force the Open ID handshake to re-authenticate, using
        # pape extension's max_auth_age, if the URL indicates it.
        if do_reauth:
            self.openid_request.addExtension(pape.Request(max_auth_age=0))

        assert not self.openid_request.shouldSendRedirect(), (
            "Our fixed OpenID server should not need us to redirect.")
        # Once the user authenticates with the OpenID provider they will be
        # sent to the /+openid-callback page, where we log them in, but
        # once that's done they must be sent back to the URL they were when
        # they started the login process (i.e. the current URL without the
        # '+login' bit). To do that we encode that URL as a query arg in the
        # return_to URL passed to the OpenID Provider
        starting_url = urllib.urlencode(
            [('starting_url', self.starting_url.encode('utf-8'))])
        trust_root = allvhosts.configs['mainsite'].rooturl
        return_to = urlappend(trust_root, '+openid-callback')
        return_to = "%s?%s" % (return_to, starting_url)
        form_html = self.openid_request.htmlMarkup(trust_root, return_to)

        # The consumer.begin() call above will insert rows into the
        # OpenIDAssociations table, but since this will be a GET request, the
        # transaction would be rolled back, so we need an explicit commit
        # here.
        transaction.commit()

        return form_html
예제 #25
0
    def render(self):
        # Reauthentication is called for by a query string parameter.
        reauth_qs = self.request.query_string_params.get('reauth', ['0'])
        do_reauth = int(reauth_qs[0])
        if self.account is not None and not do_reauth:
            return AlreadyLoggedInView(self.context, self.request)()

        # Allow unauthenticated users to have sessions for the OpenID
        # handshake to work.
        allowUnauthenticatedSession(self.request)
        consumer = self._getConsumer()
        openid_vhost = config.launchpad.openid_provider_vhost

        timeline_action = get_request_timeline(self.request).start(
            "openid-association-begin",
            allvhosts.configs[openid_vhost].rooturl,
            allow_nested=True)
        try:
            self.openid_request = consumer.begin(
                allvhosts.configs[openid_vhost].rooturl)
        finally:
            timeline_action.finish()
        self.openid_request.addExtension(
            sreg.SRegRequest(required=['email', 'fullname']))

        # Force the Open ID handshake to re-authenticate, using
        # pape extension's max_auth_age, if the URL indicates it.
        if do_reauth:
            self.openid_request.addExtension(pape.Request(max_auth_age=0))

        assert not self.openid_request.shouldSendRedirect(), (
            "Our fixed OpenID server should not need us to redirect.")
        # Once the user authenticates with the OpenID provider they will be
        # sent to the /+openid-callback page, where we log them in, but
        # once that's done they must be sent back to the URL they were when
        # they started the login process (i.e. the current URL without the
        # '+login' bit). To do that we encode that URL as a query arg in the
        # return_to URL passed to the OpenID Provider
        starting_url = urllib.urlencode([('starting_url',
                                          self.starting_url.encode('utf-8'))])
        trust_root = allvhosts.configs['mainsite'].rooturl
        return_to = urlappend(trust_root, '+openid-callback')
        return_to = "%s?%s" % (return_to, starting_url)
        form_html = self.openid_request.htmlMarkup(trust_root, return_to)

        # The consumer.begin() call above will insert rows into the
        # OpenIDAssociations table, but since this will be a GET request, the
        # transaction would be rolled back, so we need an explicit commit
        # here.
        transaction.commit()

        return form_html
예제 #26
0
 def assertRequest(self, url_suffix, **kwargs):
     [request] = self.requests
     self.assertThat(
         request,
         MatchesStructure.byEquality(url=urlappend(self.endpoint,
                                                   url_suffix),
                                     method="GET",
                                     **kwargs))
     timeline = get_request_timeline(get_current_browser_request())
     action = timeline.actions[-1]
     self.assertEqual("branch-hosting-get", action.category)
     self.assertEqual("/" + url_suffix.split("?", 1)[0],
                      action.detail.split(" ", 1)[0])
예제 #27
0
    def redirectSubTree(self, target, status=301):
        """Redirect the subtree to the given target URL."""
        while True:
            nextstep = self.request.stepstogo.consume()
            if nextstep is None:
                break
            target = urlappend(target, nextstep)

        query_string = self.request.get('QUERY_STRING')
        if query_string:
            target = target + '?' + query_string

        return RedirectionView(target, self.request, status)
예제 #28
0
    def __init__(self, baseurl, xmlrpc_transport=None,
                 internal_xmlrpc_transport=None):
        super(BugzillaAPI, self).__init__(baseurl)
        self._bugs = {}
        self._bug_aliases = {}

        self.xmlrpc_endpoint = urlappend(self.baseurl, 'xmlrpc.cgi')

        self.internal_xmlrpc_transport = internal_xmlrpc_transport
        if xmlrpc_transport is None:
            self.xmlrpc_transport = UrlLib2Transport(self.xmlrpc_endpoint)
        else:
            self.xmlrpc_transport = xmlrpc_transport
예제 #29
0
    def http_url(self):
        """Return the webapp URL for the context `LibraryFileAlias`.

        Preserve the `LibraryFileAlias.http_url` behavior for deleted
        files, returning None.

        Mask webservice requests if it's the case, so the returned URL will
        be always relative to the parent webapp URL.
        """
        if self.context.deleted:
            return None

        request = get_current_browser_request()
        if WebServiceLayer.providedBy(request):
            request = IWebBrowserOriginatingRequest(request)

        parent_url = canonical_url(self.parent, request=request)
        traversal_url = urlappend(parent_url, '+files')
        url = urlappend(
            traversal_url,
            url_path_quote(self.context.filename.encode('utf-8')))
        return url
예제 #30
0
    def redirect_branch(self):
        """Redirect /+branch/<foo> to the branch named 'foo'.

        'foo' can be the unique name of the branch, or any of the aliases for
        the branch.
        If 'foo' resolves to an ICanHasLinkedBranch instance but the linked
        branch is not yet set, redirect back to the referring page with a
        suitable notification message.
        If 'foo' is completely invalid, redirect back to the referring page
        with a suitable error message.
        """

        # The default target url to go to will be back to the referring page
        # (in the case that there is an error resolving the branch url).
        # Note: the http referer may be None if someone has hacked a url
        # directly rather than following a /+branch/<foo> link.
        target_url = self.request.getHeader('referer')
        path = '/'.join(self.request.stepstogo)
        try:
            branch, trailing = getUtility(IBranchLookup).getByLPPath(path)
            target_url = canonical_url(branch)
            if trailing != '':
                target_url = urlappend(target_url, trailing)
        except (NoLinkedBranch) as e:
            # A valid ICanHasLinkedBranch target exists but there's no
            # branch or it's not visible.

            # If are aren't arriving at this invalid branch URL from
            # another page then we just raise a NotFoundError to generate
            # a 404, otherwise we end up in a bad recursion loop. The
            # target url will be None in that case.
            if target_url is None:
                raise NotFoundError
            self.request.response.addNotification(
                "The target %s does not have a linked branch." % path)
        except (CannotHaveLinkedBranch, InvalidNamespace,
                InvalidProductName, NotFoundError) as e:
            # If are aren't arriving at this invalid branch URL from another
            # page then we just raise a NotFoundError to generate a 404,
            # otherwise we end up in a bad recursion loop. The target url will
            # be None in that case.
            if target_url is None:
                raise NotFoundError
            error_msg = str(e)
            if error_msg == '':
                error_msg = "Invalid branch lp:%s." % path
            self.request.response.addErrorNotification(error_msg)

        return self.redirectSubTree(target_url)
예제 #31
0
    def redirect_branch(self):
        """Redirect /+branch/<foo> to the branch named 'foo'.

        'foo' can be the unique name of the branch, or any of the aliases for
        the branch.
        If 'foo' resolves to an ICanHasLinkedBranch instance but the linked
        branch is not yet set, redirect back to the referring page with a
        suitable notification message.
        If 'foo' is completely invalid, redirect back to the referring page
        with a suitable error message.
        """

        # The default target url to go to will be back to the referring page
        # (in the case that there is an error resolving the branch url).
        # Note: the http referer may be None if someone has hacked a url
        # directly rather than following a /+branch/<foo> link.
        target_url = self.request.getHeader('referer')
        path = '/'.join(self.request.stepstogo)
        try:
            branch, trailing = getUtility(IBranchLookup).getByLPPath(path)
            target_url = canonical_url(branch)
            if trailing != '':
                target_url = urlappend(target_url, trailing)
        except (NoLinkedBranch) as e:
            # A valid ICanHasLinkedBranch target exists but there's no
            # branch or it's not visible.

            # If are aren't arriving at this invalid branch URL from
            # another page then we just raise a NotFoundError to generate
            # a 404, otherwise we end up in a bad recursion loop. The
            # target url will be None in that case.
            if target_url is None:
                raise NotFoundError
            self.request.response.addNotification(
                "The target %s does not have a linked branch." % path)
        except (CannotHaveLinkedBranch, InvalidNamespace,
                InvalidProductName, NotFoundError) as e:
            # If are aren't arriving at this invalid branch URL from another
            # page then we just raise a NotFoundError to generate a 404,
            # otherwise we end up in a bad recursion loop. The target url will
            # be None in that case.
            if target_url is None:
                raise NotFoundError
            error_msg = str(e)
            if error_msg == '':
                error_msg = "Invalid branch lp:%s." % path
            self.request.response.addErrorNotification(error_msg)

        return self.redirectSubTree(target_url)
예제 #32
0
    def __init__(self,
                 baseurl,
                 xmlrpc_transport=None,
                 internal_xmlrpc_transport=None):
        super(BugzillaAPI, self).__init__(baseurl)
        self._bugs = {}
        self._bug_aliases = {}

        self.xmlrpc_endpoint = urlappend(self.baseurl, 'xmlrpc.cgi')

        self.internal_xmlrpc_transport = internal_xmlrpc_transport
        if xmlrpc_transport is None:
            self.xmlrpc_transport = UrlLib2Transport(self.xmlrpc_endpoint)
        else:
            self.xmlrpc_transport = xmlrpc_transport
예제 #33
0
 def requestAuthorization(cls, snap, request):
     """Begin the process of authorizing uploads of a snap package."""
     try:
         sso_caveat_id = snap.beginAuthorization()
         base_url = canonical_url(snap, view_name='+authorize')
         login_url = urlappend(base_url, '+login')
         login_url += '?%s' % urlencode([
             ('macaroon_caveat_id', sso_caveat_id),
             ('discharge_macaroon_action', 'field.actions.complete'),
             ('discharge_macaroon_field', 'field.discharge_macaroon'),
             ])
         return login_url
     except CannotAuthorizeStoreUploads as e:
         request.response.addInfoNotification(unicode(e))
         request.response.redirect(canonical_url(snap))
         return
예제 #34
0
 def refreshDischargeMacaroon(cls, snap):
     """See `ISnapStoreClient`."""
     assert config.launchpad.openid_provider_root is not None
     assert snap.store_secrets is not None
     refresh_url = urlappend(
         config.launchpad.openid_provider_root, "api/v2/tokens/refresh")
     data = {"discharge_macaroon": snap.store_secrets["discharge"]}
     try:
         response = urlfetch(refresh_url, method="POST", json=data)
         response_data = response.json()
         if "discharge_macaroon" not in response_data:
             raise BadRefreshResponse(response.text)
         # Set a new dict here to avoid problems with security proxies.
         new_secrets = dict(snap.store_secrets)
         new_secrets["discharge"] = response_data["discharge_macaroon"]
         snap.store_secrets = new_secrets
     except requests.HTTPError as e:
         raise cls._makeSnapStoreError(BadRefreshResponse, e)
예제 #35
0
    def __init__(self, baseurl, xmlrpc_transport=None,
                 internal_xmlrpc_transport=None, cookie_jar=None):
        super(TracLPPlugin, self).__init__(baseurl)

        if cookie_jar is None:
            cookie_jar = CookieJar()
        if xmlrpc_transport is None:
            xmlrpc_transport = UrlLib2Transport(baseurl, cookie_jar)

        self._cookie_jar = cookie_jar
        self._xmlrpc_transport = xmlrpc_transport
        self._internal_xmlrpc_transport = internal_xmlrpc_transport

        xmlrpc_endpoint = urlappend(self.baseurl, 'xmlrpc')
        self._server = xmlrpclib.ServerProxy(
            xmlrpc_endpoint, transport=self._xmlrpc_transport)

        self.url_opener = urllib2.build_opener(
            urllib2.HTTPCookieProcessor(cookie_jar))
예제 #36
0
    def __init__(self,
                 baseurl,
                 xmlrpc_transport=None,
                 internal_xmlrpc_transport=None,
                 cookie_jar=None):
        super(TracLPPlugin, self).__init__(baseurl)

        if cookie_jar is None:
            cookie_jar = RequestsCookieJar()
        if xmlrpc_transport is None:
            xmlrpc_transport = RequestsTransport(baseurl, cookie_jar)

        self._cookie_jar = cookie_jar
        self._xmlrpc_transport = xmlrpc_transport
        self._internal_xmlrpc_transport = internal_xmlrpc_transport

        xmlrpc_endpoint = urlappend(self.baseurl, 'xmlrpc')
        self._server = xmlrpclib.ServerProxy(xmlrpc_endpoint,
                                             transport=self._xmlrpc_transport)
예제 #37
0
 def __call__(self):
     if IUnauthenticatedPrincipal.providedBy(self.request.principal):
         if 'loggingout' in self.request.form:
             target = '%s?loggingout=1' % self.request.URL[-2]
             self.request.response.redirect(target)
             return ''
         if self.request.method == 'POST':
             # If we got a POST then that's a problem.  We can only
             # redirect with a GET, so when we redirect after a successful
             # login, the wrong method would be used.
             # If we get a POST here, it is an application error.  We
             # must ensure that form pages require the same rights
             # as the pages that process those forms.  So, we should never
             # need to newly authenticate on a POST.
             self.request.response.setStatus(500)  # Internal Server Error
             self.request.response.setHeader('Content-type', 'text/plain')
             return ('Application error.  Unauthenticated user POSTing to '
                     'page that requires authentication.')
         # If we got any query parameters, then preserve them in the
         # new URL. Except for the BrowserNotifications
         current_url = self.request.getURL()
         while True:
             nextstep = self.request.stepstogo.consume()
             if nextstep is None:
                 break
             current_url = urlappend(current_url, nextstep)
         query_string = self.request.get('QUERY_STRING', '')
         if query_string:
             query_string = '?' + query_string
         target = self.getRedirectURL(current_url, query_string)
         # A dance to assert that we want to break the rules about no
         # unauthenticated sessions. Only after this next line is it safe
         # to use the ``addInfoNotification`` method.
         allowUnauthenticatedSession(self.request)
         self.request.response.redirect(target)
         # Maybe render page with a link to the redirection?
         return ''
     else:
         self.request.response.setStatus(403)  # Forbidden
         return self.template()
예제 #38
0
 def listChannels(cls):
     """See `ISnapStoreClient`."""
     if config.snappy.store_search_url is None:
         return _default_store_channels
     channels = None
     memcache_client = getUtility(IMemcacheClient)
     search_host = urlsplit(config.snappy.store_search_url).hostname
     memcache_key = ("%s:channels" % search_host).encode("UTF-8")
     cached_channels = memcache_client.get(memcache_key)
     if cached_channels is not None:
         try:
             channels = json.loads(cached_channels)
         except JSONDecodeError:
             log.exception(
                 "Cannot load cached channels for %s; deleting" %
                 search_host)
             memcache_client.delete(memcache_key)
     if (channels is None and
             not getFeatureFlag(u"snap.disable_channel_search")):
         path = "api/v1/channels"
         timeline = cls._getTimeline()
         if timeline is not None:
             action = timeline.start("store-search-get", "/" + path)
         channels_url = urlappend(config.snappy.store_search_url, path)
         try:
             response = urlfetch(
                 channels_url, headers={"Accept": "application/hal+json"})
         except requests.HTTPError as e:
             raise cls._makeSnapStoreError(BadSearchResponse, e)
         finally:
             if timeline is not None:
                 action.finish()
         channels = response.json().get("_embedded", {}).get(
             "clickindex:channel", [])
         expire_time = time.time() + 60 * 60 * 24
         memcache_client.set(
             memcache_key, json.dumps(channels), expire_time)
     if channels is None:
         channels = _default_store_channels
     return channels
예제 #39
0
 def __call__(self):
     if IUnauthenticatedPrincipal.providedBy(self.request.principal):
         if 'loggingout' in self.request.form:
             target = '%s?loggingout=1' % self.request.URL[-2]
             self.request.response.redirect(target)
             return ''
         if self.request.method == 'POST':
             # If we got a POST then that's a problem.  We can only
             # redirect with a GET, so when we redirect after a successful
             # login, the wrong method would be used.
             # If we get a POST here, it is an application error.  We
             # must ensure that form pages require the same rights
             # as the pages that process those forms.  So, we should never
             # need to newly authenticate on a POST.
             self.request.response.setStatus(500)  # Internal Server Error
             self.request.response.setHeader('Content-type', 'text/plain')
             return ('Application error.  Unauthenticated user POSTing to '
                     'page that requires authentication.')
         # If we got any query parameters, then preserve them in the
         # new URL. Except for the BrowserNotifications
         current_url = self.request.getURL()
         while True:
             nextstep = self.request.stepstogo.consume()
             if nextstep is None:
                 break
             current_url = urlappend(current_url, nextstep)
         query_string = self.request.get('QUERY_STRING', '')
         if query_string:
             query_string = '?' + query_string
         target = self.getRedirectURL(current_url, query_string)
         # A dance to assert that we want to break the rules about no
         # unauthenticated sessions. Only after this next line is it safe
         # to use the ``addInfoNotification`` method.
         allowUnauthenticatedSession(self.request)
         self.request.response.redirect(target)
         # Maybe render page with a link to the redirection?
         return ''
     else:
         self.request.response.setStatus(403)  # Forbidden
         return self.template()
예제 #40
0
    def __init__(self,
                 baseurl,
                 xmlrpc_transport=None,
                 internal_xmlrpc_transport=None,
                 cookie_jar=None):
        super(TracLPPlugin, self).__init__(baseurl)

        if cookie_jar is None:
            cookie_jar = CookieJar()
        if xmlrpc_transport is None:
            xmlrpc_transport = UrlLib2Transport(baseurl, cookie_jar)

        self._cookie_jar = cookie_jar
        self._xmlrpc_transport = xmlrpc_transport
        self._internal_xmlrpc_transport = internal_xmlrpc_transport

        xmlrpc_endpoint = urlappend(self.baseurl, 'xmlrpc')
        self._server = xmlrpclib.ServerProxy(xmlrpc_endpoint,
                                             transport=self._xmlrpc_transport)

        self.url_opener = urllib2.build_opener(
            urllib2.HTTPCookieProcessor(cookie_jar))
예제 #41
0
    def __init__(self,
                 baseurl,
                 xmlrpc_transport=None,
                 internal_xmlrpc_transport=None):
        super(BugzillaAPI, self).__init__(baseurl)
        self._bugs = {}
        self._bug_aliases = {}

        self.xmlrpc_endpoint = urlappend(self.baseurl, 'xmlrpc.cgi')

        self.internal_xmlrpc_transport = internal_xmlrpc_transport
        if xmlrpc_transport is None:
            self.xmlrpc_transport = RequestsTransport(self.xmlrpc_endpoint)
        else:
            self.xmlrpc_transport = xmlrpc_transport

        try:
            self.credentials
        except BugTrackerAuthenticationError:
            pass
        else:
            alsoProvides(self, ISupportsBackLinking)
            alsoProvides(self, ISupportsCommentPushing)
예제 #42
0
 def requestPackageUploadPermission(cls, snappy_series, snap_name):
     assert config.snappy.store_url is not None
     request_url = urlappend(config.snappy.store_url, "dev/api/acl/")
     request = get_current_browser_request()
     timeline_action = get_request_timeline(request).start(
         "request-snap-upload-macaroon",
         "%s/%s" % (snappy_series.name, snap_name), allow_nested=True)
     try:
         response = urlfetch(
             request_url, method="POST",
             json={
                 "packages": [
                     {"name": snap_name, "series": snappy_series.name}],
                 "permissions": ["package_upload"],
                 })
         response_data = response.json()
         if "macaroon" not in response_data:
             raise BadRequestPackageUploadResponse(response.text)
         return response_data["macaroon"]
     except requests.HTTPError as e:
         raise cls._makeSnapStoreError(BadRequestPackageUploadResponse, e)
     finally:
         timeline_action.finish()
예제 #43
0
 def href(self):
     return urlappend(self.rooturl,
                      'bugs/' + str(self.context.bug.id) + '/bug.atom')
예제 #44
0
 def href(self):
     if IAnnouncementSet.providedBy(self.context):
         return urlappend(self.rooturl, 'announcements.atom')
     else:
         return urlappend(canonical_url(self.context, rootsite='feeds'),
                          'announcements.atom')
예제 #45
0
 def href(self):
     return urlappend(self.rooturl, 'announcements.atom')
예제 #46
0
 def href(self):
     return urlappend(canonical_url(self.context, rootsite="feeds"),
                      'branch.atom')
예제 #47
0
 def href(self):
     return urlappend(canonical_url(self.context, rootsite="feeds"),
                      'revisions.atom')
 def test_trailing_path_redirect(self):
     # If there are any trailing path segments after the branch identifier,
     # these stick around at the redirected URL.
     branch = self.factory.makeAnyBranch()
     path = urlappend(branch.unique_name, '+edit')
     self.assertRedirects(path, canonical_url(branch, view_name='+edit'))
예제 #49
0
def combine_url(base, subdir, filename):
    """Combine a URL from the three parts returned by walk()."""
    subdir_url = urljoin(base, subdir)
    # The "filename" component must be appended to the resulting URL.
    return urlappend(subdir_url, filename)
예제 #50
0
 def parent_changelog_url(self, distroseriesdifference):
     """The URL to the /parent/series/+source/package/+changelog """
     distro = distroseriesdifference.parent_series.distribution
     dsp = distro.getSourcePackage(
         distroseriesdifference.source_package_name)
     return urlappend(canonical_url(dsp), '+changelog')
예제 #51
0
 def redirect_buildfarm(self):
     """Redirect old /+builds requests to new URL, /builders."""
     new_url = '/builders'
     return self.redirectSubTree(
         urlappend(new_url, '/'.join(self.request.stepstogo)))
예제 #52
0
 def href(self):
     return urlappend(canonical_url(self.context, rootsite='feeds'),
                      'latest-bugs.atom')
예제 #53
0
def notify_team_join(event):
    """Notify team admins that someone has asked to join the team.

    If the team's policy is Moderated, the email will say that the membership
    is pending approval. Otherwise it'll say that the person has joined the
    team and who added that person to the team.
    """
    person = event.person
    team = event.team
    membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
        person, team)
    assert membership is not None
    approved, admin, proposed = [
        TeamMembershipStatus.APPROVED, TeamMembershipStatus.ADMIN,
        TeamMembershipStatus.PROPOSED]
    admin_addrs = team.getTeamAdminsEmailAddresses()
    from_addr = format_address(
        team.displayname, config.canonical.noreply_from_address)

    reviewer = membership.proposed_by
    if reviewer != person and membership.status in [approved, admin]:
        reviewer = membership.reviewed_by
        # Somebody added this person as a member, we better send a
        # notification to the person too.
        member_addrs = get_contact_email_addresses(person)

        headers = {}
        if person.is_team:
            templatename = 'new-member-notification-for-teams.txt'
            subject = '%s joined %s' % (person.name, team.name)
            header_rational = "Indirect member (%s)" % team.name
            footer_rationale = (
                "You received this email because "
                "%s is the new member." % person.name)
        else:
            templatename = 'new-member-notification.txt'
            subject = 'You have been added to %s' % team.name
            header_rational = "Member (%s)" % team.name
            footer_rationale = (
                "You received this email because you are the new member.")

        if team.mailing_list is not None:
            template = get_email_template(
                'team-list-subscribe-block.txt', app='registry')
            editemails_url = urlappend(
                canonical_url(getUtility(ILaunchpadRoot)),
                'people/+me/+editemails')
            list_instructions = template % dict(editemails_url=editemails_url)
        else:
            list_instructions = ''

        template = get_email_template(templatename, app='registry')
        replacements = {
            'reviewer': '%s (%s)' % (reviewer.displayname, reviewer.name),
            'team_url': canonical_url(team),
            'member': '%s (%s)' % (person.displayname, person.name),
            'team': '%s (%s)' % (team.displayname, team.name),
            'list_instructions': list_instructions,
            }
        headers = {'X-Launchpad-Message-Rationale': header_rational}
        for address in member_addrs:
            recipient = getUtility(IPersonSet).getByEmail(address)
            replacements['recipient_name'] = recipient.displayname
            send_team_email(
                from_addr, address, subject, template, replacements,
                footer_rationale, headers)

        # The member's email address may be in admin_addrs too; let's remove
        # it so the member don't get two notifications.
        admin_addrs = set(admin_addrs).difference(set(member_addrs))

    # Yes, we can have teams with no members; not even admins.
    if not admin_addrs:
        return

    # Open teams do not notify admins about new members.
    if team.membership_policy == TeamMembershipPolicy.OPEN:
        return

    replacements = {
        'person_name': "%s (%s)" % (person.displayname, person.name),
        'team_name': "%s (%s)" % (team.displayname, team.name),
        'reviewer_name': "%s (%s)" % (reviewer.displayname, reviewer.name),
        'url': canonical_url(membership)}

    headers = {}
    if membership.status in [approved, admin]:
        template = get_email_template(
            'new-member-notification-for-admins.txt', app='registry')
        subject = '%s joined %s' % (person.name, team.name)
    elif membership.status == proposed:
        # In the UI, a user can only propose himself or a team he
        # admins. Some users of the REST API have a workflow, where
        # they propose users that are designated as mentees (Bug 498181).
        if reviewer != person:
            headers = {"Reply-To": reviewer.preferredemail.email}
            template = get_email_template(
                'pending-membership-approval-for-third-party.txt',
                app='registry')
        else:
            headers = {"Reply-To": person.preferredemail.email}
            template = get_email_template(
                'pending-membership-approval.txt', app='registry')
        subject = "%s wants to join" % person.name
    else:
        raise AssertionError(
            "Unexpected membership status: %s" % membership.status)

    for address in admin_addrs:
        recipient = getUtility(IPersonSet).getByEmail(address)
        replacements['recipient_name'] = recipient.displayname
        if recipient.is_team:
            header_rationale = 'Admin (%s via %s)' % (
                team.name, recipient.name)
            footer_rationale = (
                "you are an admin of the %s team\n"
                "via the %s team." % (
                team.displayname, recipient.displayname))
        elif recipient == team.teamowner:
            header_rationale = 'Owner (%s)' % team.name
            footer_rationale = (
                "you are the owner of the %s team." % team.displayname)
        else:
            header_rationale = 'Admin (%s)' % team.name
            footer_rationale = (
                "you are an admin of the %s team." % team.displayname)
        footer = 'You received this email because %s' % footer_rationale
        headers['X-Launchpad-Message-Rationale'] = header_rationale
        send_team_email(
            from_addr, address, subject, template, replacements,
            footer, headers)