def permission(self, name, internal=False): """Defines a permission if it hasn't been defined before. Idempotent. Raises ValueError when attempting to redeclare the permission with the same name but different attributes. Returns a reference to the permission that can be passed to `role` as an element of `includes`. """ if name.count('.') != 2: raise ValueError( 'Permissions must have form <service>.<subject>.<verb> for now, ' 'got %s' % (name, )) perm = realms_pb2.Permission(name=name, internal=internal) existing = self.permissions.get(name) if existing: if existing != perm: raise ValueError('Redeclaring permission %s' % name) else: self.permissions[name] = perm return self.PermRef(name)
def perms(*names): return [realms_pb2.Permission(name=name) for name in names]
def test_realms_serialization(self): """Serializing snapshot with non-trivial realms configs.""" realms_globals = model.AuthRealmsGlobals( key=model.realms_globals_key(), permissions=[ realms_pb2.Permission(name='luci.dev.p1'), realms_pb2.Permission(name='luci.dev.p2'), ], ) p1 = model.AuthProjectRealms( key=model.project_realms_key('proj1'), realms=realms_pb2.Realms( permissions=[{ 'name': 'luci.dev.p2' }], realms=[{ 'name': 'proj1:@root', 'bindings': [ { 'permissions': [0], 'principals': ['group:gr1'], }, ], }], ), ) p2 = model.AuthProjectRealms( key=model.project_realms_key('proj2'), realms=realms_pb2.Realms( permissions=[{ 'name': 'luci.dev.p1' }], realms=[{ 'name': 'proj2:@root', 'bindings': [ { 'permissions': [0], 'principals': ['group:gr2'], }, ], }], ), ) auth_db = make_auth_db_proto(realms_globals=realms_globals, project_realms=[p1, p2]) self.assertEqual( auth_db.realms, realms_pb2.Realms( api_version=realms.API_VERSION, permissions=[{ 'name': 'luci.dev.p1' }, { 'name': 'luci.dev.p2' }], realms=[ { 'name': 'proj1:@root', 'bindings': [ { 'permissions': [1], 'principals': ['group:gr1'], }, ], }, { 'name': 'proj2:@root', 'bindings': [ { 'permissions': [0], 'principals': ['group:gr2'], }, ], }, ], ))
def expand_realms(db, project_id, realms_cfg): """Expands realms_config_pb2.RealmsCfg into a flat realms_pb2.Realms. The returned realms_pb2.Realms contains realms and permissions of a single project only. Permissions not mentioned in the project's realms are omitted. All realms_pb2.Permission messages have names only (no metadata). api_version field is omitted. All such realms_pb2.Realms messages across all projects (plus a list of all defined permissions with all their metadata) are later merged together into a final universal realms_pb2.Realms by realms.merge(...) in components/auth/replication.py. Args: db: a permissions.DB instance with current permissions and roles. project_id: ID of a LUCI project to use as a prefix in realm names. realms_cfg: an instance of realms_config_pb2.RealmsCfg to expand. Returns: realms_pb2.Realms with expanded realms (with caveats mentioned above). Raises: ValueError if the validation fails. """ # `internal` is True when expanding internal realms (defined in a service # config file). Such realms can use internal roles and permissions and they # do not have implicit root bindings (since they are not associated with # any "project:<X>" identity used in implicit root bindings). internal = project_id == common.INTERNAL_PROJECT # The server code could have changed since the config passed the validation # and realms_cfg may not be valid anymore. Verify it still is. The code below # depends crucially on the validity of realms_cfg. validation.Validator( cfg_validation.Context.raise_on_error(), db, internal, ).validate(realms_cfg) # A lazily populated {role -> tuple of permissions} mapping. roles_expander = RolesExpander(db.roles, realms_cfg.custom_roles) # A helper to traverse the realms graph. realms_expander = RealmsExpander(roles_expander, realms_cfg.realms) # This creates @root realm and (optionally) extends it with implicit bindings. realms_expander.extend_root( db.implicit_root_bindings(project_id) if not internal else []) # Visit all realms and build preliminary bindings as pairs of # (a tuple with permission indexes, a list of principals who have them). The # bindings are preliminary since we don't know final permission indexes yet # and instead use some internal indexes as generated by RolesExpander. We # need to finish this first pass to gather the list of ALL used permissions, # so we can calculate final indexes. This is done inside of `roles_expander`. realms = [] # [(name, (permissions tuple, principals list))] for name in realms_expander.realm_names: # Build a mapping from a principal to the permissions set they have. principal_to_perms = collections.defaultdict(set) for principal, perms in realms_expander.per_principal_bindings(name): principal_to_perms[principal].update(perms) # Combine entries with the same set of permissions into one. perms_to_principals = collections.defaultdict(list) for principal, perms in principal_to_perms.items(): perms_to_principals[tuple(sorted(perms))].append(principal) realms.append((name, perms_to_principals.items())) # We now know all permissions ever used by all realms. Convert them into the # form suitable for realm_pb2 by sorting alphabetically. Keep the mapping # between old and new indexes, to be able to change indexes in permission # tuples we stored in `realms`. perms, index_map = roles_expander.sorted_permissions() # Build the final sorted form of all realms by relabeling permissions # according to the index_map and by sorting stuff. return realms_pb2.Realms( permissions=[realms_pb2.Permission(name=p) for p in perms], realms=[ realms_pb2.Realm( name='%s:%s' % (project_id, name), bindings=to_normalized_bindings(perms_to_principals, index_map), data=realms_expander.realm_data(name), ) for name, perms_to_principals in realms ])
def test_non_empty(self): self.mock_now(datetime.datetime(2014, 1, 1, 1, 1, 1)) state = model.AuthReplicationState(key=model.replication_state_key(), primary_id='blah', primary_url='https://blah', auth_db_rev=123) state.put() global_config = model.AuthGlobalConfig( key=model.root_key(), modified_ts=utils.utcnow(), modified_by=model.Identity.from_bytes('user:[email protected]'), oauth_client_id='oauth_client_id', oauth_client_secret='oauth_client_secret', oauth_additional_client_ids=['a', 'b'], token_server_url='https://token-server', security_config='security config blob') global_config.put() group = model.AuthGroup( key=model.group_key('Some group'), members=[model.Identity.from_bytes('user:[email protected]')], globs=[model.IdentityGlob.from_bytes('user:*@example.com')], nested=[], description='Some description', owners='owning-group', created_ts=utils.utcnow(), created_by=model.Identity.from_bytes('user:[email protected]'), modified_ts=utils.utcnow(), modified_by=model.Identity.from_bytes('user:[email protected]')) group.put() another = model.AuthGroup(key=model.group_key('Another group'), nested=['Some group']) another.put() ip_whitelist = model.AuthIPWhitelist( key=model.ip_whitelist_key('bots'), subnets=['127.0.0.1/32'], description='Some description', created_ts=utils.utcnow(), created_by=model.Identity.from_bytes('user:[email protected]'), modified_ts=utils.utcnow(), modified_by=model.Identity.from_bytes('user:[email protected]')) ip_whitelist.put() ip_whitelist_assignments = model.AuthIPWhitelistAssignments( key=model.ip_whitelist_assignments_key(), modified_ts=utils.utcnow(), modified_by=model.Identity.from_bytes('user:[email protected]'), assignments=[ model.AuthIPWhitelistAssignments.Assignment( identity=model.Identity.from_bytes( 'user:[email protected]'), ip_whitelist='bots', comment='some comment', created_ts=utils.utcnow(), created_by=model.Identity.from_bytes( 'user:[email protected]')), ]) ip_whitelist_assignments.put() realms_globals = model.AuthRealmsGlobals( key=model.realms_globals_key(), permissions=[ realms_pb2.Permission(name='luci.dev.p1'), realms_pb2.Permission(name='luci.dev.p2'), ]) realms_globals.put() model.AuthProjectRealms(key=model.project_realms_key('proj_id1'), realms=realms_pb2.Realms(api_version=1234), config_rev='rev1', perms_rev='rev1').put() model.AuthProjectRealms(key=model.project_realms_key('proj_id2'), realms=realms_pb2.Realms(api_version=1234), config_rev='rev2', perms_rev='rev2').put() captured_state, snapshot = replication.new_auth_db_snapshot() expected_state = { 'auth_db_rev': 123, 'modified_ts': datetime.datetime(2014, 1, 1, 1, 1, 1), 'primary_id': u'blah', 'primary_url': u'https://blah', 'shard_ids': [], } self.assertEqual(expected_state, captured_state.to_dict()) expected_snapshot = { 'global_config': { '__id__': 'root', '__parent__': None, 'auth_db_rev': None, 'auth_db_prev_rev': None, 'modified_by': model.Identity(kind='user', name='*****@*****.**'), 'modified_ts': datetime.datetime(2014, 1, 1, 1, 1, 1), 'oauth_additional_client_ids': [u'a', u'b'], 'oauth_client_id': u'oauth_client_id', 'oauth_client_secret': u'oauth_client_secret', 'security_config': 'security config blob', 'token_server_url': u'https://token-server', }, 'groups': [ { '__id__': 'Another group', '__parent__': ndb.Key('AuthGlobalConfig', 'root'), 'auth_db_rev': None, 'auth_db_prev_rev': None, 'created_by': None, 'created_ts': None, 'description': u'', 'globs': [], 'members': [], 'modified_by': None, 'modified_ts': None, 'nested': [u'Some group'], 'owners': u'administrators', }, { '__id__': 'Some group', '__parent__': ndb.Key('AuthGlobalConfig', 'root'), 'auth_db_rev': None, 'auth_db_prev_rev': None, 'created_by': model.Identity(kind='user', name='*****@*****.**'), 'created_ts': datetime.datetime(2014, 1, 1, 1, 1, 1), 'description': u'Some description', 'globs': [model.IdentityGlob(kind='user', pattern='*@example.com')], 'members': [model.Identity(kind='user', name='*****@*****.**')], 'modified_by': model.Identity(kind='user', name='*****@*****.**'), 'modified_ts': datetime.datetime(2014, 1, 1, 1, 1, 1), 'nested': [], 'owners': u'owning-group', }, ], 'ip_whitelists': [ { '__id__': 'bots', '__parent__': ndb.Key('AuthGlobalConfig', 'root'), 'auth_db_rev': None, 'auth_db_prev_rev': None, 'created_by': model.Identity(kind='user', name='*****@*****.**'), 'created_ts': datetime.datetime(2014, 1, 1, 1, 1, 1), 'description': u'Some description', 'modified_by': model.Identity(kind='user', name='*****@*****.**'), 'modified_ts': datetime.datetime(2014, 1, 1, 1, 1, 1), 'subnets': [u'127.0.0.1/32'], }, ], 'ip_whitelist_assignments': { '__id__': 'default', '__parent__': ndb.Key('AuthGlobalConfig', 'root'), 'assignments': [ { 'comment': u'some comment', 'created_by': model.Identity(kind='user', name='*****@*****.**'), 'created_ts': datetime.datetime(2014, 1, 1, 1, 1, 1), 'identity': model.Identity(kind='user', name='*****@*****.**'), 'ip_whitelist': u'bots', }, ], 'auth_db_rev': None, 'auth_db_prev_rev': None, 'modified_by': model.Identity(kind='user', name='*****@*****.**'), 'modified_ts': datetime.datetime(2014, 1, 1, 1, 1, 1), }, 'realms_globals': { '__id__': 'globals', '__parent__': ndb.Key('AuthGlobalConfig', 'root'), 'auth_db_prev_rev': None, 'auth_db_rev': None, 'modified_by': None, 'modified_ts': None, 'permissions': [ realms_pb2.Permission(name='luci.dev.p1'), realms_pb2.Permission(name='luci.dev.p2'), ], }, 'project_realms': [{ '__id__': 'proj_id1', '__parent__': ndb.Key('AuthGlobalConfig', 'root'), 'auth_db_prev_rev': None, 'auth_db_rev': None, 'config_rev': u'rev1', 'perms_rev': u'rev1', 'modified_by': None, 'modified_ts': None, 'realms': realms_pb2.Realms(api_version=1234), }, { '__id__': 'proj_id2', '__parent__': ndb.Key('AuthGlobalConfig', 'root'), 'auth_db_prev_rev': None, 'auth_db_rev': None, 'config_rev': u'rev2', 'perms_rev': u'rev2', 'modified_by': None, 'modified_ts': None, 'realms': realms_pb2.Realms(api_version=1234), }], } self.assertEqual(expected_snapshot, snapshot_to_dict(snapshot))