def issueMacaroon(self, issuer_name, context_type, context): """See `IAuthServer.issueMacaroon`.""" try: issuer = getUtility(IMacaroonIssuer, issuer_name) except ComponentLookupError: return faults.PermissionDenied() # Only permit issuers that have been specifically designed for use # with the authserver: they must need to be issued by parts of # Launchpad other than appservers but be verified by appservers, # they must take parameters that can be passed over XML-RPC, and # they must issue macaroons with carefully-designed constraints to # minimise privilege-escalation attacks. if not issuer.issuable_via_authserver: return faults.PermissionDenied() # 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.PermissionDenied() try: # issueMacaroon isn't normally public, but we clearly need it # here. macaroon = removeSecurityProxy(issuer).issueMacaroon(context) except BadMacaroonContext: return faults.PermissionDenied() return macaroon.serialize()
def create_branch(requester): if not branch_path.startswith('/'): return faults.InvalidPath(branch_path) escaped_path = unescape(branch_path.strip('/')) try: namespace, branch_name, link_func, path = ( self._getBranchNamespaceExtras(escaped_path, requester)) except ValueError: return faults.PermissionDenied("Cannot create branch at '%s'" % branch_path) except InvalidNamespace: return faults.PermissionDenied("Cannot create branch at '%s'" % branch_path) except NoSuchPerson as e: return faults.NotFound("User/team '%s' does not exist." % e.name) except NoSuchProduct as e: return faults.NotFound("Project '%s' does not exist." % e.name) except InvalidProductName as e: return faults.InvalidProductName(escape(e.name)) except NoSuchSourcePackageName as e: try: getUtility(ISourcePackageNameSet).new(e.name) except InvalidName: return faults.InvalidSourcePackageName(e.name) return self.createBranch(login_id, branch_path) except NameLookupFailed as e: return faults.NotFound(str(e)) try: branch = namespace.createBranch(BranchType.HOSTED, branch_name, requester) except LaunchpadValidationError as e: msg = e.args[0] if isinstance(msg, unicode): msg = msg.encode('utf-8') return faults.PermissionDenied(msg) except BranchCreationException as e: return faults.PermissionDenied(str(e)) if link_func: try: link_func(branch) except Unauthorized: # We don't want to keep the branch we created. transaction.abort() return faults.PermissionDenied( "Cannot create linked branch at '%s'." % path) return branch.id
def test_issueMacaroon_not_via_authserver(self): build = self.factory.makeBinaryPackageBuild( archive=self.factory.makeArchive(private=True)) private_root = getUtility(IPrivateApplication) authserver = AuthServerAPIView(private_root.authserver, TestRequest()) self.assertEqual( faults.PermissionDenied(), authserver.issueMacaroon( "binary-package-build", "BinaryPackageBuild", build))
def _parseUniqueName(self, branch_path): """Return a dict of the parsed information and the branch name.""" try: namespace_path, branch_name = branch_path.rsplit('/', 1) except ValueError: raise faults.PermissionDenied( "Cannot create branch at '/%s'" % branch_path) data = BranchNamespaceSet().parse(namespace_path) return data, branch_name
def createBranch(self, requester_id, branch_path): if not branch_path.startswith('/'): return faults.InvalidPath(branch_path) escaped_path = unescape(branch_path.strip('/')) registrant = self._person_set.get(requester_id) try: return self._createBranch(registrant, escaped_path) except LaunchpadFault as e: return e except LaunchpadValidationError as e: return faults.PermissionDenied(six.ensure_binary(e.args[0]))
def _serializeBranch(self, requester_id, branch, trailing_path, force_readonly=False): if not self._canRead(requester_id, branch): return faults.PermissionDenied() elif branch.branch_type == BranchType.REMOTE: return None if force_readonly: writable = False else: writable = self._canWrite(requester_id, branch) return ( BRANCH_TRANSPORT, {'id': branch.id, 'writable': writable}, trailing_path)
def createBranch(self, requester_id, branch_path): if not branch_path.startswith('/'): return faults.InvalidPath(branch_path) escaped_path = unescape(branch_path.strip('/')) registrant = self._person_set.get(requester_id) try: return self._createBranch(registrant, escaped_path) except LaunchpadFault as e: return e except LaunchpadValidationError as e: msg = e.args[0] if isinstance(msg, unicode): msg = msg.encode('utf-8') return faults.PermissionDenied(msg)
def _serializeBranch(self, requester, branch, trailing_path, force_readonly=False): if requester == LAUNCHPAD_SERVICES: branch = removeSecurityProxy(branch) try: branch_id = branch.id except Unauthorized: raise faults.PermissionDenied() if branch.branch_type == BranchType.REMOTE: return None if force_readonly: writable = False else: writable = self._canWriteToBranch(requester, branch) return ( BRANCH_TRANSPORT, {'id': branch_id, 'writable': writable, 'private': branch.private}, trailing_path)
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
def _createBranch(self, registrant, branch_path): """The guts of the create branch method. Raises exceptions on error conditions. """ to_link = None if branch_path.startswith(BRANCH_ALIAS_PREFIX + '/'): branch_path = branch_path[len(BRANCH_ALIAS_PREFIX) + 1:] if branch_path.startswith('~'): data, branch_name = self._parseUniqueName(branch_path) else: tokens = branch_path.split('/') data = { 'person': registrant.name, 'product': tokens[0], } branch_name = 'trunk' # check the series product = self._product_set.getByName(data['product']) if product is not None: if len(tokens) > 1: series = product.getSeries(tokens[1]) if series is None: raise faults.NotFound( "No such product series: '%s'." % tokens[1]) else: to_link = ICanHasLinkedBranch(series) else: to_link = ICanHasLinkedBranch(product) # don't forget the link. else: data, branch_name = self._parseUniqueName(branch_path) owner = self._person_set.getByName(data['person']) if owner is None: raise faults.NotFound( "User/team '%s' does not exist." % (data['person'],)) # The real code consults the branch creation policy of the product. We # don't need to do so here, since the tests above this layer never # encounter that behaviour. If they *do* change to rely on the branch # creation policy, the observed behaviour will be failure to raise # exceptions. if not registrant.inTeam(owner): raise faults.PermissionDenied( ('%s cannot create branches owned by %s' % (registrant.displayname, owner.displayname))) product = sourcepackage = None if data['product'] == '+junk': product = None elif data['product'] is not None: if not valid_name(data['product']): raise faults.InvalidProductName(escape(data['product'])) product = self._product_set.getByName(data['product']) if product is None: raise faults.NotFound( "Project '%s' does not exist." % (data['product'],)) elif data['distribution'] is not None: distro = self._distribution_set.getByName(data['distribution']) if distro is None: raise faults.NotFound( "No such distribution: '%s'." % (data['distribution'],)) distroseries = self._distroseries_set.getByName( data['distroseries']) if distroseries is None: raise faults.NotFound( "No such distribution series: '%s'." % (data['distroseries'],)) sourcepackagename = self._sourcepackagename_set.getByName( data['sourcepackagename']) if sourcepackagename is None: try: sourcepackagename = self._sourcepackagename_set.new( data['sourcepackagename']) except InvalidName: raise faults.InvalidSourcePackageName( data['sourcepackagename']) sourcepackage = self._factory.makeSourcePackage( distroseries, sourcepackagename) else: raise faults.PermissionDenied( "Cannot create branch at '%s'" % branch_path) branch = self._factory.makeBranch( owner=owner, name=branch_name, product=product, sourcepackage=sourcepackage, registrant=registrant, branch_type=BranchType.HOSTED) if to_link is not None: if registrant.inTeam(to_link.product.owner): to_link.branch = branch else: self._branch_set._delete(branch) raise faults.PermissionDenied( "Cannot create linked branch at '%s'." % branch_path) return branch.id
def _createRepository(self, requester, path, clone_from=None): try: namespace, repository_name, default_func = ( self._getGitNamespaceExtras(path, requester)) except InvalidNamespace: raise faults.PermissionDenied( "'%s' is not a valid Git repository path." % path) except NoSuchPerson as e: raise faults.NotFound("User/team '%s' does not exist." % e.name) except (NoSuchProduct, InvalidProductName) as e: raise faults.NotFound("Project '%s' does not exist." % e.name) except NoSuchSourcePackageName as e: try: getUtility(ISourcePackageNameSet).new(e.name) except InvalidName: raise faults.InvalidSourcePackageName(e.name) return self._createRepository(requester, path) except NameLookupFailed as e: raise faults.NotFound(unicode(e)) except GitRepositoryCreationForbidden as e: raise faults.PermissionDenied(unicode(e)) try: repository = namespace.createRepository( GitRepositoryType.HOSTED, requester, repository_name) except LaunchpadValidationError as e: # Despite the fault name, this just passes through the exception # text so there's no need for a new Git-specific fault. raise faults.InvalidBranchName(e) except GitRepositoryExists as e: # We should never get here, as we just tried to translate the # path and found nothing (not even an inaccessible private # repository). Log an OOPS for investigation. self._reportError(path, e) except GitRepositoryCreationException as e: raise faults.PermissionDenied(unicode(e)) try: if default_func: try: default_func(repository) except Unauthorized: raise faults.PermissionDenied( "You cannot set the default Git repository for '%s'." % path) # Flush to make sure that repository.id is populated. Store.of(repository).flush() assert repository.id is not None # If repository has target_default, clone from default. target_path = None try: default = self.repository_set.getDefaultRepository( repository.target) if default is not None and default.visibleByUser(requester): target_path = default.getInternalPath() else: default = self.repository_set.getDefaultRepositoryForOwner( repository.owner, repository.target) if (default is not None and default.visibleByUser(requester)): target_path = default.getInternalPath() except GitTargetError: pass # Ignore Personal repositories. hosting_path = repository.getInternalPath() try: getUtility(IGitHostingClient).create( hosting_path, clone_from=target_path) except GitRepositoryCreationFault as e: # The hosting service failed. Log an OOPS for investigation. self._reportError(path, e, hosting_path=hosting_path) except Exception: # We don't want to keep the repository we created. transaction.abort() raise
def test_issue_bad_context(self): build = self.factory.makeSnapBuild() self.assertEqual( faults.PermissionDenied(), self.authserver.issueMacaroon('test', 'SnapBuild', build.id))
def test_issue_not_issuable_via_authserver(self): self.issuer.issuable_via_authserver = False self.assertEqual( faults.PermissionDenied(), self.authserver.issueMacaroon('test', 'LibraryFileAlias', 1))
def test_issue_wrong_context_type(self): self.assertEqual( faults.PermissionDenied(), self.authserver.issueMacaroon('unknown-issuer', 'nonsense', 1))
def test_issue_unknown_issuer(self): self.assertEqual( faults.PermissionDenied(), self.authserver.issueMacaroon('unknown-issuer', 'LibraryFileAlias', 1))