Beispiel #1
0
 def xmlrpc_verifyMacaroon(self, macaroon_raw, context_type, context):
     if context_type != 'LibraryFileAlias':
         return faults.Unauthorized()
     if (macaroon_raw, context) in self.macaroons:
         return True
     else:
         return faults.Unauthorized()
Beispiel #2
0
 def test_verify_wrong_context(self):
     lfa = getUtility(ILibraryFileAliasSet)[1]
     macaroon = self.issuer.issueMacaroon(lfa)
     self.assertEqual(
         faults.Unauthorized(),
         self.authserver.verifyMacaroon(macaroon.serialize(),
                                        'LibraryFileAlias', 2))
Beispiel #3
0
 def test_verify_unknown_issuer(self):
     macaroon = Macaroon(location=config.vhost.mainsite.hostname,
                         identifier='unknown-issuer',
                         key='test')
     self.assertEqual(
         faults.Unauthorized(),
         self.authserver.verifyMacaroon(macaroon.serialize(),
                                        'LibraryFileAlias', 1))
Beispiel #4
0
 def verifyMacaroon(self, macaroon_raw, context_type, context):
     """See `IAuthServer.verifyMacaroon`."""
     try:
         macaroon = Macaroon.deserialize(macaroon_raw)
     # XXX cjwatson 2019-04-23: Restrict exceptions once
     # https://github.com/ecordell/pymacaroons/issues/50 is fixed.
     except Exception:
         return faults.Unauthorized()
     try:
         issuer = getUtility(IMacaroonIssuer, macaroon.identifier)
     except ComponentLookupError:
         return faults.Unauthorized()
     # The context is plain data, since we can't pass general objects over
     # the XML-RPC interface.  Look it up so that we can verify it.
     context = self._resolveContext(context_type, context)
     if context is None:
         return faults.Unauthorized()
     if not issuer.verifyMacaroon(macaroon, context):
         return faults.Unauthorized()
     return True
Beispiel #5
0
 def test_verify_nonexistent_lfa(self):
     macaroon = self.issuer.issueMacaroon(
         getUtility(ILibraryFileAliasSet)[1])
     # Pick a large ID that doesn't exist in sampledata.
     lfa_id = 1000000
     self.assertRaises(SQLObjectNotFound,
                       getUtility(ILibraryFileAliasSet).__getitem__, lfa_id)
     self.assertEqual(
         faults.Unauthorized(),
         self.authserver.verifyMacaroon(macaroon.serialize(),
                                        'LibraryFileAlias', lfa_id))
Beispiel #6
0
 def authenticateWithPassword(self, username, password):
     """See `IGitAPI`."""
     # XXX cjwatson 2016-10-06: We only support free-floating macaroons
     # at the moment, not ones bound to a user.
     if not username:
         verified = self._verifyMacaroon(password)
         if verified:
             auth_params = {"macaroon": password}
             if _is_issuer_internal(verified):
                 auth_params["user"] = LAUNCHPAD_SERVICES
             return auth_params
     # Only macaroons are supported for password authentication.
     return faults.Unauthorized()
Beispiel #7
0
 def _translatePath(self, requester, path, permission, auth_params):
     if requester == LAUNCHPAD_ANONYMOUS:
         requester = None
     try:
         result = self._performLookup(requester, path, auth_params)
         if (result is None and requester is not None and
             permission == "write"):
             self._createRepository(requester, path)
             result = self._performLookup(requester, path, auth_params)
         if result is None:
             raise faults.GitRepositoryNotFound(path)
         if permission != "read" and not result["writable"]:
             raise faults.PermissionDenied()
         return result
     except (faults.PermissionDenied, faults.GitRepositoryNotFound):
         # Turn lookup errors for anonymous HTTP requests into
         # "authorisation required", so that the user-agent has a
         # chance to try HTTP basic auth.
         can_authenticate = auth_params.get("can-authenticate", False)
         if can_authenticate and requester is None:
             raise faults.Unauthorized()
         else:
             raise
Beispiel #8
0
    def _checkRefPermissions(self, requester, translated_path, ref_paths,
                             auth_params):
        if requester == LAUNCHPAD_ANONYMOUS:
            requester = None
        repository = removeSecurityProxy(
            getUtility(IGitLookup).getByHostingPath(translated_path))
        if repository is None:
            raise faults.GitRepositoryNotFound(translated_path)

        try:
            macaroon_raw = auth_params.get("macaroon")
            if macaroon_raw is not None:
                verified = self._verifyMacaroon(macaroon_raw, repository)
                if not verified:
                    # Macaroon authentication failed.  Don't fall back to
                    # the requester's permissions, since macaroons typically
                    # have additional constraints.
                    raise faults.Unauthorized()

                # Internal macaroons may only be used by internal services,
                # and user macaroons may only be used by real users.  Forbid
                # potential confusion.
                internal = _is_issuer_internal(verified)
                if (requester == LAUNCHPAD_SERVICES) != internal:
                    raise faults.Unauthorized()

                if internal:
                    if not _can_internal_issuer_write(verified):
                        raise faults.Unauthorized()

                    # We know that the authentication parameters
                    # specifically grant access to this repository because
                    # we were able to verify the macaroon using the
                    # repository as its context, so we can bypass other
                    # checks and grant access as an anonymous repository
                    # owner.  This is only permitted for selected macaroon
                    # issuers used by internal services.
                    requester = GitGranteeType.REPOSITORY_OWNER
            elif requester == LAUNCHPAD_SERVICES:
                # Internal services must authenticate using a macaroon.
                raise faults.Unauthorized()
        except faults.Unauthorized:
            # XXX cjwatson 2019-05-09: It would be simpler to just raise
            # this directly, but turnip won't handle it very gracefully at
            # the moment.  It's possible to reach this by being very unlucky
            # about the timing of a push.
            return [
                (xmlrpc_client.Binary(ref_path.data), [])
                for ref_path in ref_paths]

        if all(isinstance(ref_path, xmlrpc_client.Binary)
               for ref_path in ref_paths):
            # New protocol: caller sends paths as bytes; Launchpad returns a
            # list of (path, permissions) tuples.  (XML-RPC doesn't support
            # dict keys being bytes.)
            ref_paths = [ref_path.data for ref_path in ref_paths]
            return [
                (xmlrpc_client.Binary(ref_path),
                 self._renderPermissions(permissions))
                for ref_path, permissions in repository.checkRefPermissions(
                    requester, ref_paths).items()
                ]
        else:
            # Old protocol: caller sends paths as text; Launchpad returns a
            # dict of {path: permissions}.
            # XXX cjwatson 2018-11-21: Remove this once turnip has migrated
            # to the new protocol.  git ref paths are not required to be
            # valid UTF-8.
            return {
                ref_path: self._renderPermissions(permissions)
                for ref_path, permissions in repository.checkRefPermissions(
                    requester, ref_paths).items()
                }
Beispiel #9
0
    def _performLookup(self, requester, path, auth_params):
        repository, extra_path = getUtility(IGitLookup).getByPath(path)
        if repository is None:
            return None

        macaroon_raw = auth_params.get("macaroon")
        naked_repository = removeSecurityProxy(repository)
        writable = None

        if macaroon_raw is not None:
            verified = self._verifyMacaroon(macaroon_raw, naked_repository)
            if not verified:
                # Macaroon authentication failed.  Don't fall back to the
                # requester's permissions, since macaroons typically have
                # additional constraints.  Instead, just return
                # "authorisation required", thus preventing probing for the
                # existence of repositories without presenting valid
                # credentials.
                raise faults.Unauthorized()

            # Internal macaroons may only be used by internal services, and
            # user macaroons may only be used by real users.  Forbid
            # potential confusion.
            internal = _is_issuer_internal(verified)
            if (requester == LAUNCHPAD_SERVICES) != internal:
                raise faults.Unauthorized()

            if internal:
                # We know that the authentication parameters specifically
                # grant access to this repository because we were able to
                # verify the macaroon using the repository as its context,
                # so we can bypass other checks.  This is only permitted for
                # selected macaroon issuers used by internal services.
                hosting_path = naked_repository.getInternalPath()
                writable = _can_internal_issuer_write(verified)
                private = naked_repository.private

            # In any other case, the macaroon constrains the permissions of
            # the principal, so fall through to doing normal user
            # authorisation.
        elif requester == LAUNCHPAD_SERVICES:
            # Internal services must authenticate using a macaroon.
            raise faults.Unauthorized()

        if writable is None:
            # This isn't an authorised internal service, so perform normal
            # user authorisation.
            try:
                hosting_path = repository.getInternalPath()
            except Unauthorized:
                return None
            writable = (
                repository.repository_type == GitRepositoryType.HOSTED and
                check_permission("launchpad.Edit", repository))
            # If we have any grants to this user, they are declared to have
            # write access at this point. `_checkRefPermissions` will
            # sort out access to individual refs at a later point in the push.
            if not writable:
                grants = naked_repository.findRuleGrantsByGrantee(requester)
                if not grants.is_empty():
                    writable = True
            private = repository.private
        return {
            "path": hosting_path,
            "writable": writable,
            "trailing": extra_path,
            "private": private,
            }
Beispiel #10
0
 def test_verify_nonsense_macaroon(self):
     self.assertEqual(
         faults.Unauthorized(),
         self.authserver.verifyMacaroon('nonsense', 'LibraryFileAlias', 1))