def discharge(ctx, content, key, locator, checker): '''Handles a discharge request as received by the /discharge endpoint. @param ctx The context passed to the checker {checkers.AuthContext} @param content URL and form parameters {dict} @param locator Locator used to add third party caveats returned by the checker {macaroonbakery.ThirdPartyLocator} @param checker {macaroonbakery.ThirdPartyCaveatChecker} Used to check third party caveats. @return The discharge macaroon {macaroonbakery.Macaroon} ''' id = content.get('id') if id is None: id = content.get('id64') if id is not None: id = utils.b64decode(id) caveat = content.get('caveat64') if caveat is not None: caveat = utils.b64decode(caveat) return bakery.discharge( ctx, id=id, caveat=caveat, key=key, checker=checker, locator=locator, )
def discharge(ctx, content, key, locator, checker): '''Handles a discharge request as received by the /discharge endpoint. @param ctx The context passed to the checker {checkers.AuthContext} @param content URL and form parameters {dict} @param locator Locator used to add third party caveats returned by the checker {macaroonbakery.ThirdPartyLocator} @param checker Used to check third party caveats {macaroonbakery.ThirdPartyCaveatChecker} @return The discharge macaroon {macaroonbakery.Macaroon} ''' id = content.get('id') if id is None: id = content.get('id64') if id is not None: id = utils.b64decode(id) caveat = content.get('caveat64') if caveat is not None: caveat = utils.b64decode(caveat) return bakery.discharge( ctx, id=id, caveat=caveat, key=key, checker=checker, locator=locator, )
def interact(self, client, location, interaction_required_err): '''Implement Interactor.interact by obtaining obtaining a macaroon from the discharger, discharging it with the local private key using the discharged macaroon as a discharge token''' p = interaction_required_err.interaction_method('agent', InteractionInfo) if p.login_url is None or p.login_url == '': raise httpbakery.InteractionError( 'no login-url field found in agent interaction method') agent = self._find_agent(location) if not location.endswith('/'): location += '/' login_url = urljoin(location, p.login_url) resp = requests.get(login_url, json={ 'Username': agent.username, 'PublicKey': self._auth_info.key.encode().decode('utf-8'), }) if resp.status_code != 200: raise httpbakery.InteractionError( 'cannot acquire agent macaroon: {}'.format(resp.status_code) ) m = resp.json().get('macaroon') if m is None: raise httpbakery.InteractionError('no macaroon in response') m = bakery.Macaroon.from_dict(m) ms = bakery.discharge_all(m, None, self._auth_info.key) b = bytearray() for m in ms: b.extend(utils.b64decode(m.serialize())) return httpbakery.DischargeToken(kind='agent', value=bytes(b))
def interact(self, client, location, interaction_required_err): '''Implement Interactor.interact by obtaining obtaining a macaroon from the discharger, discharging it with the local private key using the discharged macaroon as a discharge token''' p = interaction_required_err.interaction_method( 'agent', InteractionInfo) if p.login_url is None or p.login_url == '': raise httpbakery.InteractionError( 'no login-url field found in agent interaction method') agent = self._find_agent(location) if not location.endswith('/'): location += '/' login_url = urljoin(location, p.login_url) resp = requests.get(login_url, json={ 'Username': agent.username, 'PublicKey': self._auth_info.key.encode().decode('utf-8'), }) if resp.status_code != 200: raise httpbakery.InteractionError( 'cannot acquire agent macaroon: {}'.format(resp.status_code)) m = resp.json().get('macaroon') if m is None: raise httpbakery.InteractionError('no macaroon in response') m = bakery.Macaroon.from_dict(m) ms = bakery.discharge_all(m, None, self._auth_info.key) b = bytearray() for m in ms: b.extend(utils.b64decode(m.serialize())) return httpbakery.DischargeToken(kind='agent', value=bytes(b))
def from_dict(cls, json_dict): '''Return a macaroon obtained from the given dictionary as deserialized from JSON. @param json_dict The deserialized JSON object. ''' json_macaroon = json_dict.get('m') if json_macaroon is None: # Try the v1 format if we don't have a macaroon field. m = pymacaroons.Macaroon.deserialize( json.dumps(json_dict), json_serializer.JsonSerializer()) macaroon = Macaroon(root_key=None, id=None, namespace=bakery.legacy_namespace(), version=_bakery_version(m.version)) macaroon._macaroon = m return macaroon version = json_dict.get('v', None) if version is None: raise ValueError('no version specified') if (version < bakery.VERSION_3 or version > bakery.LATEST_VERSION): raise ValueError('unknown bakery version {}'.format(version)) m = pymacaroons.Macaroon.deserialize(json.dumps(json_macaroon), json_serializer.JsonSerializer()) if m.version != macaroon_version(version): raise ValueError('underlying macaroon has inconsistent version; ' 'got {} want {}'.format( m.version, macaroon_version(version))) namespace = checkers.deserialize_namespace(json_dict.get('ns')) cdata = json_dict.get('cdata', {}) caveat_data = {} for id64 in cdata: id = utils.b64decode(id64) data = utils.b64decode(cdata[id64]) caveat_data[id] = data macaroon = Macaroon(root_key=None, id=None, namespace=namespace, version=version) macaroon._caveat_data = caveat_data macaroon._macaroon = m return macaroon
def from_dict(cls, json_dict): '''Return a macaroon obtained from the given dictionary as deserialized from JSON. @param json_dict The deserialized JSON object. ''' json_macaroon = json_dict.get('m') if json_macaroon is None: # Try the v1 format if we don't have a macaroon field. m = pymacaroons.Macaroon.deserialize( json.dumps(json_dict), json_serializer.JsonSerializer()) macaroon = Macaroon(root_key=None, id=None, namespace=bakery.legacy_namespace(), version=_bakery_version(m.version)) macaroon._macaroon = m return macaroon version = json_dict.get('v', None) if version is None: raise ValueError('no version specified') if (version < bakery.VERSION_3 or version > bakery.LATEST_VERSION): raise ValueError('unknown bakery version {}'.format(version)) m = pymacaroons.Macaroon.deserialize(json.dumps(json_macaroon), json_serializer.JsonSerializer()) if m.version != macaroon_version(version): raise ValueError( 'underlying macaroon has inconsistent version; ' 'got {} want {}'.format(m.version, macaroon_version(version))) namespace = checkers.deserialize_namespace(json_dict.get('ns')) cdata = json_dict.get('cdata', {}) caveat_data = {} for id64 in cdata: id = utils.b64decode(id64) data = utils.b64decode(cdata[id64]) caveat_data[id] = data macaroon = Macaroon(root_key=None, id=None, namespace=namespace, version=version) macaroon._caveat_data = caveat_data macaroon._macaroon = m return macaroon
def _decode_macaroon_id(id): storage_id = b'' base64_decoded = False first = id[:1] if first == b'A': # The first byte is not a version number and it's 'A', which is the # base64 encoding of the top 6 bits (all zero) of the version number 2 # or 3, so we assume that it's the base64 encoding of a new-style # macaroon id, so we base64 decode it. # # Note that old-style ids always start with an ASCII character >= 4 # (> 32 in fact) so this logic won't be triggered for those. try: dec = utils.b64decode(id.decode('utf-8')) # Set the id only on success. id = dec base64_decoded = True except: # if it's a bad encoding, we'll get an error which is fine pass # Trim any extraneous information from the id before retrieving # it from storage, including the UUID that's added when # creating macaroons to make all macaroons unique even if # they're using the same root key. first = six.byte2int(id[:1]) if first == bakery.VERSION_2: # Skip the UUID at the start of the id. storage_id = id[1 + 16:] if first == bakery.VERSION_3: try: id1 = id_pb2.MacaroonId.FromString(id[1:]) except google.protobuf.message.DecodeError: raise bakery.VerificationError( 'no operations found in macaroon') if len(id1.ops) == 0 or len(id1.ops[0].actions) == 0: raise bakery.VerificationError( 'no operations found in macaroon') ops = [] for op in id1.ops: for action in op.actions: ops.append(bakery.Op(op.entity, action)) return id1.storageId, ops if not base64_decoded and _is_lower_case_hex_char(first): # It's an old-style id, probably with a hyphenated UUID. # so trim that off. last = id.rfind(b'-') if last >= 0: storage_id = id[0:last] return storage_id, [bakery.LOGIN_OP]
def _decode_macaroon_id(id): storage_id = b'' base64_decoded = False first = id[:1] if first == b'A': # The first byte is not a version number and it's 'A', which is the # base64 encoding of the top 6 bits (all zero) of the version number 2 # or 3, so we assume that it's the base64 encoding of a new-style # macaroon id, so we base64 decode it. # # Note that old-style ids always start with an ASCII character >= 4 # (> 32 in fact) so this logic won't be triggered for those. try: dec = utils.b64decode(id.decode('utf-8')) # Set the id only on success. id = dec base64_decoded = True except: # if it's a bad encoding, we'll get an error which is fine pass # Trim any extraneous information from the id before retrieving # it from storage, including the UUID that's added when # creating macaroons to make all macaroons unique even if # they're using the same root key. first = six.byte2int(id[:1]) if first == bakery.VERSION_2: # Skip the UUID at the start of the id. storage_id = id[1 + 16:] if first == bakery.VERSION_3: try: id1 = id_pb2.MacaroonId.FromString(id[1:]) except google.protobuf.message.DecodeError: raise bakery.VerificationError('no operations found in macaroon') if len(id1.ops) == 0 or len(id1.ops[0].actions) == 0: raise bakery.VerificationError('no operations found in macaroon') ops = [] for op in id1.ops: for action in op.actions: ops.append(bakery.Op(op.entity, action)) return id1.storageId, ops if not base64_decoded and _is_lower_case_hex_char(first): # It's an old-style id, probably with a hyphenated UUID. # so trim that off. last = id.rfind(b'-') if last >= 0: storage_id = id[0:last] return storage_id, [bakery.LOGIN_OP]
def add_macaroon(data): data = utils.b64decode(data) data_as_objs = json.loads(data.decode('utf-8')) ms = [utils.macaroon_from_dict(x) for x in data_as_objs] mss.append(ms)