Chromium Code Reviews| Index: appengine/auth_service/delegation.py |
| diff --git a/appengine/auth_service/delegation.py b/appengine/auth_service/delegation.py |
| index c2cb4d3a76e64f459d3a9257c75fc2f4614e5517..e41aa40fad7a5589cc545f1525c2f1928829c4aa 100644 |
| --- a/appengine/auth_service/delegation.py |
| +++ b/appengine/auth_service/delegation.py |
| @@ -7,10 +7,13 @@ |
| import logging |
| import webapp2 |
| +from google.appengine.ext import ndb |
| + |
| from components import auth |
| from components import utils |
| from components.auth import delegation |
| +from components.auth import ipaddr |
| from components.auth.proto import delegation_pb2 |
| from proto import config_pb2 |
| @@ -78,9 +81,10 @@ class CreateDelegationTokenHandler(auth.ApiHandler): |
| raise auth.AuthorizationError( |
| 'This API call must not be used with active delegation token') |
| - # Convert request body to proto (with validation). |
| + # Convert request body to proto (with validation). Verify IP format. |
| try: |
| subtoken = subtoken_from_jsonish(self.parse_body()) |
| + caller_ip = ipaddr.ip_from_string(self.request.remote_addr) |
|
nodir
2016/07/20 18:04:22
ip is not provided by the user, so perhaps it shou
Vadim Sh.
2016/07/20 20:07:07
Done. Replaced with get_peer_ip, it is already val
|
| except (TypeError, ValueError) as exc: |
| self.abort_with_error(400, text=str(exc)) |
| @@ -98,7 +102,10 @@ class CreateDelegationTokenHandler(auth.ApiHandler): |
| subtoken.services[:] = get_default_allowed_services(user_id) |
| # Check ACL (raises auth.AuthorizationError on errors). |
| - check_can_create_token(user_id, subtoken) |
| + rule = check_can_create_token(user_id, subtoken) |
| + |
| + # Register the token in the datastore, generate its ID. |
| + subtoken.subtoken_id = register_subtoken(subtoken, rule, caller_ip) |
| # Create and sign the token. |
| try: |
| @@ -183,6 +190,9 @@ def subtoken_from_jsonish(d): |
| return msg |
| +################################################################################ |
| + |
| + |
| # Fallback rule returned if nothing else matches. |
| DEFAULT_RULE = config_pb2.DelegationConfig.Rule( |
| user_id=['*'], |
| @@ -243,6 +253,9 @@ def check_can_create_token(user_id, subtoken): |
| user_id: identity string of a current caller. |
| subtoken: instance of delegation_pb2.Subtoken describing root token. |
| + Returns: |
| + config_pb2.DelegationConfig.Rule that allows the operation. |
| + |
| Raises: |
| auth.AuthorizationError if such token is not allowed for the caller. |
| """ |
| @@ -255,13 +268,13 @@ def check_can_create_token(user_id, subtoken): |
| # Just delegating one's own identity (not impersonating someone else)? Allow. |
| if subtoken.issuer_id == user_id: |
| - return |
| + return rule |
| # Verify it's OK to impersonate a given user. |
| impersonated = auth.Identity.from_bytes(subtoken.issuer_id) |
| for principal_set in rule.allowed_to_impersonate: |
| if is_identity_in_principal_set(impersonated, principal_set): |
| - return |
| + return rule |
| raise auth.AuthorizationError( |
| '"%s" is not allowed to impersonate "%s" on %s' % |
| @@ -277,3 +290,62 @@ def get_default_allowed_services(user_id): |
| rule = get_delegation_rule(user_id, ['*']) |
| return rule.target_service |
| + |
| +################################################################################ |
| + |
| + |
| +class AuthDelegationSubtoken(ndb.Model): |
| + """Represents a delegation subtoken. |
| + |
| + Used to track what tokens are issued. Root entity. ID is autogenerated. |
| + """ |
| + # Serialized delegation_pb2.Subtoken proto. |
| + subtoken = ndb.BlobProperty() |
| + # Serialized config_pb2.DelegationConfig.Rule that allowed this token. |
| + rule = ndb.BlobProperty() |
| + # IP address the minting request came from. |
| + caller_ip = ndb.StringProperty() |
| + # Version of the auth_service that created the subtoken. |
| + auth_service_version = ndb.StringProperty() |
| + |
| + # Fields below are extracted from 'subtoken', for indexing purposes. |
| + |
| + # Whose authority the token conveys. |
| + issuer_id = ndb.StringProperty() |
| + # When the token was created. |
| + creation_time = ndb.DateTimeProperty() |
| + # List of service that accept the token (or empty list if all). |
|
nodir
2016/07/20 18:04:22
services
Vadim Sh.
2016/07/20 20:07:06
Done.
|
| + services = ndb.StringProperty(repeated=True) |
| + # Who initiated the minting request if it is an impersonation token. |
| + impersonator_id = ndb.StringProperty() |
| + |
| + |
| +def register_subtoken(subtoken, rule, caller_ip): |
| + """Creates new AuthDelegationSubtoken entity in the datastore, returns its ID. |
| + |
| + Args: |
| + subtoken: delegation_pb2.Subtoken describing the token. |
| + rule: config_pb2.DelegationConfig.Rule that allows the operation |
| + caller_ip: ipaddr.IP of the caller. |
| + |
| + Returns: |
| + int64 with ID of the new entity. |
| + """ |
| + entity = AuthDelegationSubtoken( |
| + subtoken=subtoken.SerializeToString(), |
| + rule=rule.SerializeToString(), |
| + caller_ip=ipaddr.ip_to_string(caller_ip), |
| + auth_service_version=utils.get_app_version(), |
| + issuer_id=subtoken.issuer_id, |
| + creation_time=utils.timestamp_to_datetime(subtoken.creation_time*1e6), |
|
nodir
2016/07/20 18:04:22
using datetime.fromtimestamp is probably more stra
Vadim Sh.
2016/07/20 20:07:06
It returns local time, not UTC.
|
| + services=list(subtoken.services or []), |
| + impersonator_id=subtoken.impersonator_id) |
| + entity.put(use_cache=False, use_memcache=False) |
| + subtoken_id = entity.key.integer_id() |
| + |
| + # Keep a logging entry (extractable via BigQuery) too. |
| + logging.info( |
| + 'subtoken: subtoken_id=%d caller_ip=%s issuer_id=%s impersonator_id=%s', |
| + subtoken_id, entity.caller_ip, entity.issuer_id, entity.impersonator_id) |
| + |
| + return subtoken_id |