예제 #1
0
    def test_get_process_auth_db_exceptions(self):
        """Ensure get_process_auth_db() handles DB exceptions well."""
        # Prepare several instances of AuthDB to be used in mocks.
        auth_db_v0 = api.AuthDB(entity_group_version=0)
        auth_db_v1 = api.AuthDB(entity_group_version=1)

        # Fetch initial copy of AuthDB.
        self.set_time(0)
        self.set_fetched_auth_db(auth_db_v0)
        self.assertEqual(auth_db_v0, api.get_process_auth_db())

        # Make process cache expire.
        self.set_time(api.get_process_cache_expiration_sec() + 1)

        # Emulate an exception in fetch_auth_db.
        def mock_fetch_auth_db(*_kwargs):
            raise Exception('Boom!')

        self.mock(api, 'fetch_auth_db', mock_fetch_auth_db)

        # Capture calls to logging.exception.
        logger_calls = []
        self.mock(api.logging, 'exception',
                  lambda *_args: logger_calls.append(1))

        # Should return older copy of auth_db_v0 and log the exception.
        self.assertEqual(auth_db_v0, api.get_process_auth_db())
        self.assertEqual(1, len(logger_calls))

        # Make fetch_auth_db to work again. Verify get_process_auth_db() works too.
        self.set_fetched_auth_db(auth_db_v1)
        self.assertEqual(auth_db_v1, api.get_process_auth_db())
예제 #2
0
 def test_allowed_clock_drift(self):
     now = utils.utcnow()
     self.mock_now(now)
     tok = fake_subtoken_proto('user:[email protected]')
     # Works -29 sec before activation.
     self.mock_now(now, -29)
     self.assertTrue(
         delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB()))
     # Doesn't work before that.
     self.mock_now(now, -31)
     with self.assertRaises(delegation.BadTokenError):
         delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB())
예제 #3
0
 def test_expiration_moment(self):
     now = utils.utcnow()
     self.mock_now(now)
     tok = fake_subtoken_proto('user:[email protected]',
                               validity_duration=3600)
     # Active at now + 3599.
     self.mock_now(now, 3599)
     self.assertTrue(
         delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB()))
     # Expired at now + 3601.
     self.mock_now(now, 3601)
     with self.assertRaises(delegation.BadTokenError):
         delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB())
예제 #4
0
 def test_subtoken_services(self):
     tok = fake_subtoken_proto('user:[email protected]',
                               services=['service:app-id'])
     # Passes.
     self.mock(model, 'get_service_self_identity',
               lambda: model.Identity.from_bytes('service:app-id'))
     self.assertTrue(
         delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB()))
     # Fails.
     self.mock(model, 'get_service_self_identity',
               lambda: model.Identity.from_bytes('service:another-app-id'))
     with self.assertRaises(delegation.BadTokenError):
         delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB())
예제 #5
0
    def test_get_process_auth_db_multithreading(self):
        """Ensure get_process_auth_db() plays nice with multiple threads."""
        def run_in_thread(func):
            """Runs |func| in a parallel thread, returns future (as Queue)."""
            result = Queue.Queue()
            thread = threading.Thread(target=lambda: result.put(func()))
            thread.start()
            return result

        # Prepare several instances of AuthDB to be used in mocks.
        auth_db_v0 = api.AuthDB(entity_group_version=0)
        auth_db_v1 = api.AuthDB(entity_group_version=1)

        # Run initial fetch, should cache |auth_db_v0| in process cache.
        self.set_time(0)
        self.set_fetched_auth_db(auth_db_v0)
        self.assertEqual(auth_db_v0, api.get_process_auth_db())

        # Make process cache expire.
        self.set_time(api.get_process_cache_expiration_sec() + 1)

        # Start fetching AuthDB from another thread, at some point it will call
        # 'fetch_auth_db', and we pause the thread then and resume main thread.
        fetching_now = threading.Event()
        auth_db_queue = Queue.Queue()

        def mock_fetch_auth_db(**_kwargs):
            fetching_now.set()
            return auth_db_queue.get()

        self.mock(api, 'fetch_auth_db', mock_fetch_auth_db)
        future = run_in_thread(api.get_process_auth_db)

        # Wait for internal thread to call |fetch_auth_db|.
        fetching_now.wait()

        # Ok, now main thread is unblocked, while internal thread is blocking on a
        # artificially slow 'fetch_auth_db' call. Main thread can now try to get
        # AuthDB via get_process_auth_db(). It should get older stale copy right
        # away.
        self.assertEqual(auth_db_v0, api.get_process_auth_db())

        # Finish background 'fetch_auth_db' call by returning 'auth_db_v1'.
        # That's what internal thread should get as result of 'get_process_auth_db'.
        auth_db_queue.put(auth_db_v1)
        self.assertEqual(auth_db_v1, future.get())

        # Now main thread should get it as well.
        self.assertEqual(auth_db_v1, api.get_process_auth_db())
예제 #6
0
    def test_get_group(self):
        g = model.AuthGroup(
            key=model.group_key('group'),
            members=[
                model.Identity.from_bytes('user:[email protected]'),
                model.Identity.from_bytes('user:[email protected]'),
            ],
            globs=[model.IdentityGlob.from_bytes('user:*')],
            nested=['blah'],
            created_by=model.Identity.from_bytes('user:[email protected]'),
            created_ts=datetime.datetime(2014, 1, 2, 3, 4, 5),
            modified_by=model.Identity.from_bytes('user:[email protected]'),
            modified_ts=datetime.datetime(2015, 1, 2, 3, 4, 5))

        db = api.AuthDB(groups=[g])

        # Unknown group.
        self.assertIsNone(db.get_group('blah'))

        # Known group.
        from_cache = db.get_group('group')
        self.assertEqual(from_cache.key, g.key)

        # Members list is sorted.
        self.assertEqual(from_cache.members, [
            model.Identity.from_bytes('user:[email protected]'),
            model.Identity.from_bytes('user:[email protected]'),
        ])

        # Fields that are know to be different.
        exclude = ['members', 'auth_db_rev', 'auth_db_prev_rev']
        self.assertEqual(from_cache.to_dict(exclude=exclude),
                         g.to_dict(exclude=exclude))
예제 #7
0
 def test_expired(self):
     now = int(utils.time_time())
     tok = fake_subtoken_proto('user:[email protected]',
                               creation_time=now - 120,
                               validity_duration=60)
     with self.assertRaises(delegation.BadTokenError):
         delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB())
예제 #8
0
    def test_get_secret(self):
        # Make AuthDB with two secrets.
        secret = model.AuthSecret.bootstrap('some_secret')
        auth_db = api.AuthDB(secrets=[secret])

        # Ensure they are accessible via get_secret.
        self.assertEqual(secret.values,
                         auth_db.get_secret(api.SecretKey('some_secret')))
예제 #9
0
    def test_get_process_auth_db_known_version(self):
        """Ensure get_process_auth_db() respects entity group version."""
        # Prepare several instances of AuthDB to be used in mocks.
        auth_db_v0 = api.AuthDB(entity_group_version=0)
        auth_db_v0_again = api.AuthDB(entity_group_version=0)

        # Fetch initial copy of AuthDB.
        self.set_time(0)
        self.set_fetched_auth_db(auth_db_v0)
        self.assertEqual(auth_db_v0, api.get_process_auth_db())

        # Make cache expire, but setup fetch_auth_db to return a new instance of
        # AuthDB, but with same entity group version. Old known instance of AuthDB
        # should be reused.
        self.set_time(api.get_process_cache_expiration_sec() + 1)
        self.set_fetched_auth_db(auth_db_v0_again)
        self.assertTrue(api.get_process_auth_db() is auth_db_v0)
예제 #10
0
 def test_is_allowed_oauth_client_id(self):
     global_config = model.AuthGlobalConfig(
         oauth_client_id='1', oauth_additional_client_ids=['2', '3'])
     auth_db = api.AuthDB(global_config=global_config)
     self.assertFalse(auth_db.is_allowed_oauth_client_id(None))
     self.assertTrue(auth_db.is_allowed_oauth_client_id('1'))
     self.assertTrue(auth_db.is_allowed_oauth_client_id('2'))
     self.assertTrue(auth_db.is_allowed_oauth_client_id('3'))
     self.assertFalse(auth_db.is_allowed_oauth_client_id('4'))
예제 #11
0
    def test_get_latest_auth_db(self):
        """Ensure get_latest_auth_db "rushes" cached AuthDB update."""
        auth_db_v0 = api.AuthDB(replication_state=mock_replication_state(0))
        auth_db_v1 = api.AuthDB(replication_state=mock_replication_state(1))

        # Fetch initial copy of AuthDB.
        self.set_time(0)
        self.set_fetched_auth_db(auth_db_v0)
        self.assertEqual(auth_db_v0, api.get_process_auth_db())

        # Rig up fetch_auth_db to return a newer version.
        self.set_fetched_auth_db(auth_db_v1)

        # 'get_process_auth_db' still returns the cached one.
        self.assertEqual(auth_db_v0, api.get_process_auth_db())

        # But 'get_latest_auth_db' returns a new one and updates the cached copy.
        self.assertEqual(auth_db_v1, api.get_latest_auth_db())
        self.assertEqual(auth_db_v1, api.get_process_auth_db())
예제 #12
0
    def test_get_process_auth_db_expiration(self):
        """Ensure get_process_auth_db() respects expiration."""
        # Prepare several instances of AuthDB to be used in mocks.
        auth_db_v0 = api.AuthDB(entity_group_version=0)
        auth_db_v1 = api.AuthDB(entity_group_version=1)

        # Fetch initial copy of AuthDB.
        self.set_time(0)
        self.set_fetched_auth_db(auth_db_v0)
        self.assertEqual(auth_db_v0, api.get_process_auth_db())

        # It doesn't expire for some time.
        self.set_time(api.get_process_cache_expiration_sec() - 1)
        self.set_fetched_auth_db(auth_db_v1)
        self.assertEqual(auth_db_v0, api.get_process_auth_db())

        # But eventually it does.
        self.set_time(api.get_process_cache_expiration_sec() + 1)
        self.set_fetched_auth_db(auth_db_v1)
        self.assertEqual(auth_db_v1, api.get_process_auth_db())
예제 #13
0
    def test_get_secret(self):
        # Make AuthDB with two secrets.
        local_secret = model.AuthSecret.bootstrap('local_secret', 'local')
        global_secret = model.AuthSecret.bootstrap('global_secret', 'global')
        auth_db = api.AuthDB(secrets=[local_secret, global_secret])

        # Ensure they are accessible via get_secret.
        self.assertEqual(
            local_secret.values,
            auth_db.get_secret(api.SecretKey('local_secret', 'local')))
        self.assertEqual(
            global_secret.values,
            auth_db.get_secret(api.SecretKey('global_secret', 'global')))
예제 #14
0
 def test_is_allowed_oauth_client_id(self):
     global_config = model.AuthGlobalConfig(
         oauth_client_id='1', oauth_additional_client_ids=['2', '3'])
     auth_db = api.AuthDB(global_config=global_config,
                          additional_client_ids=['local'])
     self.assertFalse(auth_db.is_allowed_oauth_client_id(None))
     self.assertTrue(auth_db.is_allowed_oauth_client_id('1'))
     self.assertTrue(auth_db.is_allowed_oauth_client_id('2'))
     self.assertTrue(auth_db.is_allowed_oauth_client_id('3'))
     self.assertTrue(auth_db.is_allowed_oauth_client_id('local'))
     self.assertTrue(
         auth_db.is_allowed_oauth_client_id(api.API_EXPLORER_CLIENT_ID))
     self.assertFalse(auth_db.is_allowed_oauth_client_id('4'))
예제 #15
0
 def test_verify_ip_whitelisted_missing_whitelist(self):
     auth_db = api.AuthDB(
         ip_whitelist_assignments=model.AuthIPWhitelistAssignments(
             assignments=[
                 model.AuthIPWhitelistAssignments.Assignment(
                     identity=model.Identity(model.IDENTITY_USER,
                                             '*****@*****.**'),
                     ip_whitelist='missing ip whitelist',
                 )
             ], ), )
     with self.assertRaises(api.AuthorizationError):
         auth_db.verify_ip_whitelisted(
             model.Identity(model.IDENTITY_USER, '*****@*****.**'),
             ipaddr.ip_from_string('127.0.0.1'), {})
예제 #16
0
    def test_nested_groups_cycle(self):
        # Groups that nest each other.
        group1 = model.AuthGroup(id='Group1')
        group1.nested.append('Group2')
        group2 = model.AuthGroup(id='Group2')
        group2.nested.append('Group1')

        # Collect error messages.
        errors = []
        self.mock(api.logging, 'error', lambda *args: errors.append(args))

        # This should not hang, but produce error message.
        auth_db = api.AuthDB(groups=[group1, group2])
        self.assertFalse(auth_db.is_group_member('Group1', model.Anonymous))
        self.assertEqual(1, len(errors))
예제 #17
0
  def test_get_secret_bootstrap(self):
    # Mock AuthSecret.bootstrap to capture calls to it.
    original = api.model.AuthSecret.bootstrap
    calls = []
    @classmethod
    def mocked_bootstrap(cls, name, scope):
      calls.append((name, scope))
      result = original(name, scope)
      result.values = ['123']
      return result
    self.mock(api.model.AuthSecret, 'bootstrap', mocked_bootstrap)

    auth_db = api.AuthDB()
    got = auth_db.get_secret(api.SecretKey('local_secret', 'local'))
    self.assertEqual(['123'], got)
    self.assertEqual([('local_secret', 'local')], calls)
예제 #18
0
    def test_nested_groups_cycle(self):
        # Groups that nest each other.
        group1 = model.AuthGroup(id='Group1')
        group1.nested.append('Group2')
        group2 = model.AuthGroup(id='Group2')
        group2.nested.append('Group1')

        # Collect warnings.
        warnings = []
        self.mock(api.logging, 'warning',
                  lambda msg, *_args: warnings.append(msg))

        # This should not hang, but produce error message.
        auth_db = api.AuthDB(groups=[group1, group2])
        self.assertFalse(auth_db.is_group_member('Group1', model.Anonymous))
        self.assertEqual(1, len(warnings))
        self.assertTrue('Cycle in a group graph' in warnings[0])
예제 #19
0
 def test_subtoken_audience(self):
     auth_db = api.AuthDB(groups=[
         model.AuthGroup(
             id='abc',
             members=[model.Identity.from_bytes('user:[email protected]')],
         )
     ])
     tok = fake_subtoken_proto('user:[email protected]',
                               audience=['user:[email protected]', 'group:abc'])
     # Works.
     make_id = model.Identity.from_bytes
     self.assertTrue(
         delegation.check_subtoken(tok, make_id('user:[email protected]'), auth_db))
     self.assertTrue(
         delegation.check_subtoken(tok, make_id('user:[email protected]'), auth_db))
     # Other ids are rejected.
     with self.assertRaises(delegation.BadTokenError):
         delegation.check_subtoken(tok, make_id('user:[email protected]'), auth_db)
예제 #20
0
  def test_not_real_nested_group_cycle_aka_issue_251(self):
    # See https://github.com/luci/luci-py/issues/251.
    #
    # B -> A, C -> [B, A]. When traversing C, A is seen twice, and this is fine.
    group_A = model.AuthGroup(id='A')
    group_B = model.AuthGroup(id='B')
    group_C = model.AuthGroup(id='C')

    group_B.nested = ['A']
    group_C.nested = ['A', 'B']

    db = api.AuthDB(groups=[group_A, group_B, group_C])

    # 'is_group_member' must not report 'Cycle in a group graph' warning.
    warnings = []
    self.mock(api.logging, 'warning', lambda msg, *_args: warnings.append(msg))
    self.assertFalse(db.is_group_member('C', model.Anonymous))
    self.assertFalse(warnings)
예제 #21
0
    def test_is_group_member(self):
        # Test identity.
        joe = model.Identity(model.IDENTITY_USER, '*****@*****.**')

        # Group that includes joe via glob.
        with_glob = model.AuthGroup(id='WithGlob')
        with_glob.globs.append(
            model.IdentityGlob(model.IDENTITY_USER, '*@example.com'))

        # Group that includes joe via explicit listing.
        with_listing = model.AuthGroup(id='WithListing')
        with_listing.members.append(joe)

        # Group that includes joe via nested group.
        with_nesting = model.AuthGroup(id='WithNesting')
        with_nesting.nested.append('WithListing')

        # Creates AuthDB with given list of groups and then runs the check.
        is_member = (lambda groups, identity, group: api.AuthDB(groups=groups).
                     is_group_member(group, identity))

        # Wildcard group includes everyone (even anonymous).
        self.assertTrue(is_member([], joe, '*'))
        self.assertTrue(is_member([], model.Anonymous, '*'))

        # An unknown group includes nobody.
        self.assertFalse(is_member([], joe, 'Missing'))
        self.assertFalse(is_member([], model.Anonymous, 'Missing'))

        # Globs are respected.
        self.assertTrue(is_member([with_glob], joe, 'WithGlob'))
        self.assertFalse(is_member([with_glob], model.Anonymous, 'WithGlob'))

        # Members lists are respected.
        self.assertTrue(is_member([with_listing], joe, 'WithListing'))
        self.assertFalse(
            is_member([with_listing], model.Anonymous, 'WithListing'))

        # Nested groups are respected.
        self.assertTrue(
            is_member([with_nesting, with_listing], joe, 'WithNesting'))
        self.assertFalse(
            is_member([with_nesting, with_listing], model.Anonymous,
                      'WithNesting'))
예제 #22
0
 def make_auth_db_with_ip_whitelist():
   """AuthDB with [email protected] assigned IP whitelist '127.0.0.1/32'."""
   return api.AuthDB(
     ip_whitelists=[
       model.AuthIPWhitelist(
         key=model.ip_whitelist_key('some ip whitelist'),
         subnets=['127.0.0.1/32'],
       ),
       model.AuthIPWhitelist(
         key=model.ip_whitelist_key('bots'),
         subnets=['192.168.1.1/32', '::1/32'],
       ),
     ],
     ip_whitelist_assignments=model.AuthIPWhitelistAssignments(
       assignments=[
         model.AuthIPWhitelistAssignments.Assignment(
           identity=model.Identity(model.IDENTITY_USER, '*****@*****.**'),
           ip_whitelist='some ip whitelist',)
       ],
     ),
   )
예제 #23
0
    def test_list_group(self):
        list_group = (lambda groups, group, recursive: api.AuthDB(
            groups=groups).list_group(group, recursive))

        grp_1 = model.AuthGroup(id='1')
        grp_1.members.extend([
            model.Identity(model.IDENTITY_USER, '*****@*****.**'),
            model.Identity(model.IDENTITY_USER, '*****@*****.**'),
        ])

        grp_2 = model.AuthGroup(id='2')
        grp_2.nested.append('1')
        grp_2.members.extend([
            # Specify 'b' again, even though it's in a nested group.
            model.Identity(model.IDENTITY_USER, '*****@*****.**'),
            model.Identity(model.IDENTITY_USER, '*****@*****.**'),
        ])

        # Unknown group.
        self.assertEqual(set(), list_group([grp_1, grp_2], 'blah', False))
        self.assertEqual(set(), list_group([grp_1, grp_2], 'blah', True))

        # Non recursive.
        expected = set([
            model.Identity(model.IDENTITY_USER, '*****@*****.**'),
            model.Identity(model.IDENTITY_USER, '*****@*****.**'),
        ])
        self.assertEqual(expected, list_group([grp_1, grp_2], '2', False))

        # Recursive.
        expected = set([
            model.Identity(model.IDENTITY_USER, '*****@*****.**'),
            model.Identity(model.IDENTITY_USER, '*****@*****.**'),
            model.Identity(model.IDENTITY_USER, '*****@*****.**'),
        ])
        self.assertEqual(expected, list_group([grp_1, grp_2], '2', True))
예제 #24
0
 def list_group(groups, group, recursive):
     l = api.AuthDB(groups=groups).list_group(group, recursive)
     return api.GroupListing(sorted(l.members), sorted(l.globs),
                             sorted(l.nested))
예제 #25
0
 def build(self):
     return api.AuthDB(groups=self.groups)
예제 #26
0
 def test_passes_validation(self):
     tok = fake_subtoken_proto('user:[email protected]')
     ident = delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB())
     self.assertEqual('user:[email protected]', ident.to_bytes())
예제 #27
0
 def test_negative_validatity_duration(self):
     tok = fake_subtoken_proto('user:[email protected]',
                               validity_duration=-3600)
     with self.assertRaises(delegation.BadTokenError):
         delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB())
예제 #28
0
 def test_not_active_yet(self):
     now = int(utils.time_time())
     tok = fake_subtoken_proto('user:[email protected]',
                               creation_time=now + 120)
     with self.assertRaises(delegation.BadTokenError):
         delegation.check_subtoken(tok, FAKE_IDENT, api.AuthDB())
예제 #29
0
 def test_get_secret_bad_scope(self):
     with self.assertRaises(ValueError):
         api.AuthDB().get_secret(api.SecretKey('some', 'bad-scope'))