def test_api_key_already_revoked_by_developer(self):
     self.key.update(is_active=None)
     tasks.revoke_api_key(self.key.id)
     # If the key has already been revoked, there is no active key,
     # so `get_jwt_key` raises `DoesNotExist`.
     with pytest.raises(APIKey.DoesNotExist):
         APIKey.get_jwt_key(user_id=self.user.id)
Esempio n. 2
0
    def test_coauthor_api_key_in_submission_is_found(self):
        coauthor = user_factory()
        AddonUser.objects.create(addon=self.addon, user_id=coauthor.id)
        upload = self.get_upload(abspath=self.file,
                                 with_validation=False,
                                 addon=self.addon,
                                 user=coauthor)
        tasks.validate(upload, listed=True)

        upload.refresh_from_db()

        assert upload.processed_validation['errors'] == 1
        messages = upload.processed_validation['messages']
        assert len(messages) == 1
        assert messages[0]['id'] == [
            'validation', 'messages', 'api_key_detected'
        ]
        assert ('The developer API key of a coauthor was found in the '
                'submitted file.' in messages[0]['message'])
        assert not upload.valid

        # If the key has been revoked, there is no active key,
        # so `get_jwt_key` raises `DoesNotExist`.
        with pytest.raises(APIKey.DoesNotExist):
            APIKey.get_jwt_key(user_id=self.user.id)

        assert len(mail.outbox) == 1
        assert 'Your AMO API credentials have been revoked' in mail.outbox[
            0].subject
        assert 'never share your credentials' in mail.outbox[0].body
        # We submit as the coauthor, the leaked key is the one from 'self.user'
        assert mail.outbox[0].to[0] == self.user.email
    def test_api_key_in_submission_is_found(self):
        upload = FileUpload.objects.create(path=self.file,
                                           addon=self.addon,
                                           user=self.user)
        tasks.validate(upload, listed=True)

        upload.refresh_from_db()

        assert upload.processed_validation['errors'] == 1
        messages = upload.processed_validation['messages']
        assert len(messages) == 1
        assert messages[0]['id'] == [
            u'validation', u'messages', u'api_key_detected'
        ]
        assert ('Your developer API key was found in the submitted '
                'file.' in messages[0]['message'])
        assert not upload.valid

        # If the key has been revoked, there is no active key,
        # so `get_jwt_key` raises `DoesNotExist`.
        with pytest.raises(APIKey.DoesNotExist):
            APIKey.get_jwt_key(user_id=self.user.id)

        assert len(mail.outbox) == 1
        assert ('Your AMO API credentials have been revoked'
                in mail.outbox[0].subject)
        assert ('never share your credentials' in mail.outbox[0].body)
        assert mail.outbox[0].to[0] == self.user.email
 def test_api_key_already_regenerated_by_developer(self):
     self.key.update(is_active=None)
     current_key = APIKey.new_jwt_credentials(user=self.user)
     tasks.revoke_api_key(self.key.id)
     key_from_db = APIKey.get_jwt_key(user_id=self.user.id)
     assert current_key.key == key_from_db.key
     assert current_key.secret == key_from_db.secret
Esempio n. 5
0
 def test_api_key_already_revoked_by_developer(self):
     self.key.update(is_active=None)
     tasks.revoke_api_key(self.key.id)
     # If the key has already been revoked, there is no active key,
     # so `get_jwt_key` raises `DoesNotExist`.
     with pytest.raises(APIKey.DoesNotExist):
         APIKey.get_jwt_key(user_id=self.user.id)
Esempio n. 6
0
 def test_api_key_already_regenerated_by_developer(self):
     self.key.update(is_active=None)
     current_key = APIKey.new_jwt_credentials(user=self.user)
     tasks.revoke_api_key(self.key.id)
     key_from_db = APIKey.get_jwt_key(user_id=self.user.id)
     assert current_key.key == key_from_db.key
     assert current_key.secret == key_from_db.secret
Esempio n. 7
0
    def test_coauthor_api_key_in_submission_is_found(self):
        coauthor = user_factory()
        AddonUser.objects.create(addon=self.addon, user_id=coauthor.id)
        upload = FileUpload.objects.create(path=self.file, addon=self.addon,
                                           user=coauthor)
        tasks.validate(upload, listed=True)

        upload.refresh_from_db()

        assert upload.processed_validation['errors'] == 1
        messages = upload.processed_validation['messages']
        assert len(messages) == 1
        assert messages[0]['id'] == [
            u'validation', u'messages', u'api_key_detected']
        assert ('The developer API key of a coauthor was found in the '
                'submitted file.' in messages[0]['message'])
        assert not upload.valid

        # If the key has been revoked, there is no active key,
        # so `get_jwt_key` raises `DoesNotExist`.
        with pytest.raises(APIKey.DoesNotExist):
            APIKey.get_jwt_key(user_id=self.user.id)

        assert len(mail.outbox) == 1
        assert ('Your AMO API credentials have been revoked'
                in mail.outbox[0].subject)
        assert ('never share your credentials' in mail.outbox[0].body)
        # We submit as the coauthor, the leaked key is the one from 'self.user'
        assert mail.outbox[0].to[0] == self.user.email
Esempio n. 8
0
def revoke_api_key(key_id):
    try:
        # Fetch the original key, do not use `get_jwt_key`
        # so we get access to a user object for logging later.
        original_key = APIKey.objects.get(type=SYMMETRIC_JWT_TYPE, id=key_id)
        # Fetch the current key to compare to the original,
        # throws if the key has been revoked, which also means
        # `original_key` is not active.
        current_key = APIKey.get_jwt_key(user_id=original_key.user.id)
        if current_key.key != original_key.key:
            log.info(
                'User %s has already regenerated the key, nothing to be '
                'done.' % original_key.user
            )
        else:
            with transaction.atomic():
                log.info('Revoking key for user %s.' % current_key.user)
                current_key.update(is_active=None)
                send_api_key_revocation_email(emails=[current_key.user.email])
    except APIKey.DoesNotExist:
        log.info(
            'User %s has already revoked the key, nothing to be done.'
            % original_key.user
        )
        pass
Esempio n. 9
0
def check_for_api_keys_in_file(results, upload_pk):
    upload = FileUpload.objects.get(pk=upload_pk)

    if upload.addon:
        users = upload.addon.authors.all()
    else:
        users = [upload.user] if upload.user else []

    keys = []
    for user in users:
        try:
            key = APIKey.get_jwt_key(user_id=user.id)
            keys.append(key)
        except APIKey.DoesNotExist:
            pass

    try:
        if len(keys) > 0:
            zipfile = SafeZip(source=upload.path)
            for zipinfo in zipfile.info_list:
                if zipinfo.file_size >= 64:
                    file_ = zipfile.read(zipinfo)
                    for key in keys:
                        if key.secret in file_.decode(errors='ignore'):
                            log.info('Developer API key for user %s found in '
                                     'submission.' % key.user)
                            if key.user == upload.user:
                                msg = gettext('Your developer API key was '
                                              'found in the submitted file. '
                                              'To protect your account, the '
                                              'key will be revoked.')
                            else:
                                msg = gettext('The developer API key of a '
                                              'coauthor was found in the '
                                              'submitted file. To protect '
                                              'your add-on, the key will be '
                                              'revoked.')
                            annotations.insert_validation_message(
                                results,
                                type_='error',
                                message=msg,
                                msg_id='api_key_detected',
                                compatibility_type=None,
                            )

                            # Revoke after 2 minutes to allow the developer to
                            # fetch the validation results
                            revoke_api_key.apply_async(
                                kwargs={'key_id': key.id}, countdown=120)
            zipfile.close()
    except (ValidationError, BadZipFile, IOError):
        pass

    return results
    def test_incorrect_signature(self):
        api_key = self.create_api_key(self.user)
        token = self.create_auth_token(api_key.user, api_key.key,
                                       api_key.secret)

        decoy_api_key = APIKey(  # Don't save in database, it would conflict.
            user=self.user, key=api_key.key, secret='another-secret')

        with self.assertRaises(jwt.DecodeError) as ctx:
            jwt_auth.jwt_decode_handler(
                token, get_api_key=lambda **k: decoy_api_key)

        assert str(ctx.exception) == 'Signature verification failed'
Esempio n. 11
0
 def handle(self, *args, **options):
     revoked_count = 0
     with open(options['csv_file']) as csvfile:
         for idx, (key, secret) in enumerate(csv.reader(csvfile), start=1):
             try:
                 apikey = APIKey.objects.get(key=key, is_active=True)
             except APIKey.DoesNotExist:
                 self.stdout.write(
                     f'Ignoring APIKey {key}, it does not exist.\n')
                 continue
             if apikey.secret != secret:
                 self.stdout.write(
                     f'Ignoring APIKey {key}, secret differs.\n')
                 continue
             else:
                 with transaction.atomic():
                     apikey.update(is_active=None)
                     APIKey.new_jwt_credentials(user=apikey.user)
                 revoked_count += 1
                 self.stdout.write(f'Revoked APIKey {key}.\n')
         self.stdout.write(
             f'Done. Revoked {revoked_count} keys out of {idx} entries.')
Esempio n. 12
0
    def handle(self, *args, **options):
        user_data = {}

        # Do quick and dirty validation if --noinput
        if not options.get('interactive', True):
            # Stolen from django's `createsuperuser` implementation.
            try:
                for field_name in self.required_fields:
                    if options.get(field_name, None):
                        field = self.UserModel._meta.get_field(field_name)
                        user_data[field_name] = field.clean(options[field_name], None)
                    else:
                        raise CommandError(
                            'You must use --%s with --noinput.' % field_name
                        )
            except exceptions.ValidationError as exc:
                raise CommandError('; '.join(exc.messages))
        else:
            user_data = {
                field_name: self.get_value(field_name)
                for field_name in self.required_fields
            }

        if options.get('fxa_id', None):
            field = self.UserModel._meta.get_field('fxa_id')
            user_data['fxa_id'] = field.clean(options['fxa_id'], None)

        user = get_user_model()._default_manager.create_superuser(**user_data)

        if options.get('add_to_supercreate_group', False):
            user.read_dev_agreement = datetime.utcnow()
            user.save(update_fields=('read_dev_agreement',))

            group, _ = Group.objects.get_or_create(
                rules='Accounts:SuperCreate',
                defaults={'name': 'Account Super Creators'},
            )
            GroupUser.objects.create(user=user, group=group)
            apikey = APIKey.new_jwt_credentials(user=user)

            self.stdout.write(
                json.dumps(
                    {
                        'username': user.username,
                        'email': user.email,
                        'api-key': apikey.key,
                        'api-secret': apikey.secret,
                        'fxa-id': user.fxa_id,
                    }
                )
            )
Esempio n. 13
0
    def test_api_key_in_new_submission_is_found(self):
        upload = FileUpload.objects.create(path=self.file, user=self.user)
        tasks.validate(upload, listed=True)

        upload.refresh_from_db()

        assert upload.processed_validation['errors'] == 1
        messages = upload.processed_validation['messages']
        assert len(messages) == 1
        assert messages[0]['id'] == [
            u'validation', u'messages', u'api_key_detected']
        assert ('Your developer API key was found in the submitted '
                'file.' in messages[0]['message'])
        assert not upload.valid

        # If the key has been revoked, there is no active key,
        # so `get_jwt_key` raises `DoesNotExist`.
        with pytest.raises(APIKey.DoesNotExist):
            APIKey.get_jwt_key(user_id=self.user.id)

        assert len(mail.outbox) == 1
        assert ('Your AMO API credentials have been revoked'
                in mail.outbox[0].subject)
        assert mail.outbox[0].to[0] == self.user.email
 def handle(self, *args, **options):
     revoked_count = 0
     with open(options['csv_file'], 'rb') as csvfile:
         for idx, (key, secret) in enumerate(csv.reader(csvfile), start=1):
             try:
                 apikey = APIKey.objects.get(key=key, is_active=True)
             except APIKey.DoesNotExist:
                 self.stdout.write(
                     'Ignoring APIKey {}, it does not exist.\n'.format(key))
                 continue
             if apikey.secret != secret:
                 self.stdout.write(
                     'Ignoring APIKey {}, secret differs.\n'.format(key))
                 continue
             else:
                 with transaction.atomic():
                     apikey.update(is_active=None)
                     APIKey.new_jwt_credentials(user=apikey.user)
                 revoked_count += 1
                 self.stdout.write(
                     'Revoked APIKey {}.\n'.format(key))
         self.stdout.write(
             'Done. Revoked {} keys out of {} entries.'.format(
                 revoked_count, idx))
Esempio n. 15
0
def check_for_api_keys_in_file(results, upload):
    if upload.addon:
        users = upload.addon.authors.all()
    else:
        users = [upload.user] if upload.user else []

    keys = []
    for user in users:
        try:
            key = APIKey.get_jwt_key(user_id=user.id)
            keys.append(key)
        except APIKey.DoesNotExist:
            pass

    if len(keys) > 0:
        zipfile = SafeZip(source=upload.path)
        zipfile.is_valid()
        for zipinfo in zipfile.info_list:
            if zipinfo.file_size >= 64:
                file_ = zipfile.read(zipinfo)
                for key in keys:
                    if key.secret in file_.decode(encoding='unicode-escape',
                                                  errors="ignore"):
                        log.info('Developer API key for user %s found in '
                                 'submission.' % key.user)
                        if key.user == upload.user:
                            msg = ugettext('Your developer API key was found '
                                           'in the submitted file. To protect '
                                           'your account, the key will be '
                                           'revoked.')
                        else:
                            msg = ugettext('The developer API key of a '
                                           'coauthor was found in the '
                                           'submitted file. To protect your '
                                           'add-on, the key will be revoked.')
                        insert_validation_message(
                            results, type_='error',
                            message=msg, msg_id='api_key_detected',
                            compatibility_type=None)

                        # Revoke after 2 minutes to allow the developer to
                        # fetch the validation results
                        revoke_api_key.apply_async(
                            kwargs={'key_id': key.id}, countdown=120)
        zipfile.close()

    return results
Esempio n. 16
0
    def handle(self, *args, **options):
        user_data = {}

        # Do quick and dirty validation if --noinput
        if not options.get('interactive', True):
            # Stolen from django's `createsuperuser` implementation.
            try:
                for field_name in self.required_fields:
                    if options.get(field_name, None):
                        field = self.UserModel._meta.get_field(field_name)
                        user_data[field_name] = field.clean(
                            options[field_name], None)
                    else:
                        raise CommandError(
                            'You must use --%s with --noinput.' % field_name)
            except exceptions.ValidationError as exc:
                raise CommandError('; '.join(exc.messages))
        else:
            user_data = {
                field_name: self.get_value(field_name)
                for field_name in self.required_fields
            }

        if options.get('fxa_id', None):
            field = self.UserModel._meta.get_field('fxa_id')
            user_data['fxa_id'] = field.clean(
                options['fxa_id'], None)

        user = get_user_model()._default_manager.create_superuser(**user_data)

        if options.get('add_to_supercreate_group', False):
            user.read_dev_agreement = datetime.utcnow()
            user.save(update_fields=('read_dev_agreement',))

            group, _ = Group.objects.get_or_create(
                rules='Accounts:SuperCreate',
                defaults={'name': 'Account Super Creators'})
            GroupUser.objects.create(user=user, group=group)
            apikey = APIKey.new_jwt_credentials(user=user)

            self.stdout.write(json.dumps({
                'username': user.username,
                'email': user.email,
                'api-key': apikey.key,
                'api-secret': apikey.secret,
                'fxa-id': user.fxa_id,
            }))
Esempio n. 17
0
    def test_api_key_does_not_exist(self):
        user = user_factory()
        # The test csv does not contain an entry for this user.
        apikey = APIKey.new_jwt_credentials(user=user)
        old_secret = apikey.secret
        stdout = io.StringIO()
        call_command('revoke_api_keys', self.csv_path, stdout=stdout)
        stdout.seek(0)
        output = stdout.readlines()
        assert output[0] == ('Ignoring APIKey user:12345:666, it does not exist.\n')
        assert output[1] == ('Ignoring APIKey user:67890:333, it does not exist.\n')

        # APIKey is still active, secret hasn't changed, there are no
        # additional APIKeys.
        apikey.reload()
        assert apikey.secret == old_secret
        assert apikey.is_active
        assert APIKey.objects.filter(user=user).count() == 1
Esempio n. 18
0
    def authenticate_credentials(self, payload):
        """
        Returns a verified AMO user who is active and allowed to make API
        requests.
        """
        try:
            api_key = APIKey.get_jwt_key(key=payload['iss'])
        except APIKey.DoesNotExist:
            msg = 'Invalid API Key.'
            raise exceptions.AuthenticationFailed(msg)

        if api_key.user.deleted:
            msg = 'User account is disabled.'
            raise exceptions.AuthenticationFailed(msg)
        if not api_key.user.read_dev_agreement:
            msg = 'User has not read developer agreement.'
            raise exceptions.AuthenticationFailed(msg)

        return api_key.user
Esempio n. 19
0
    def authenticate_credentials(self, payload):
        """
        Returns a verified AMO user who is active and allowed to make API
        requests.
        """
        try:
            api_key = APIKey.get_jwt_key(key=payload['iss'])
        except APIKey.DoesNotExist:
            msg = 'Invalid API Key.'
            raise exceptions.AuthenticationFailed(msg)

        if api_key.user.deleted:
            msg = 'User account is disabled.'
            raise exceptions.AuthenticationFailed(msg)
        if not api_key.user.read_dev_agreement:
            msg = 'User has not read developer agreement.'
            raise exceptions.AuthenticationFailed(msg)

        return api_key.user
    def test_api_key_does_not_exist(self):
        user = user_factory()
        # The test csv does not contain an entry for this user.
        apikey = APIKey.new_jwt_credentials(user=user)
        old_secret = apikey.secret
        stdout = StringIO()
        call_command('revoke_api_keys', self.csv_path, stdout=stdout)
        stdout.seek(0)
        output = stdout.readlines()
        assert output[0] == (
            'Ignoring APIKey user:12345:666, it does not exist.\n')
        assert output[1] == (
            'Ignoring APIKey user:67890:333, it does not exist.\n')

        # APIKey is still active, secret hasn't changed, there are no
        # additional APIKeys.
        apikey.reload()
        assert apikey.secret == old_secret
        assert apikey.is_active
        assert APIKey.objects.filter(user=user).count() == 1
Esempio n. 21
0
def revoke_api_key(key_id):
    try:
        # Fetch the original key, do not use `get_jwt_key`
        # so we get access to a user object for logging later.
        original_key = APIKey.objects.get(
            type=SYMMETRIC_JWT_TYPE, id=key_id)
        # Fetch the current key to compare to the original,
        # throws if the key has been revoked, which also means
        # `original_key` is not active.
        current_key = APIKey.get_jwt_key(user_id=original_key.user.id)
        if current_key.key != original_key.key:
            log.info('User %s has already regenerated the key, nothing to be '
                     'done.' % original_key.user)
        else:
            with transaction.atomic():
                log.info('Revoking key for user %s.' % current_key.user)
                current_key.update(is_active=None)
                send_api_key_revocation_email(emails=[current_key.user.email])
    except APIKey.DoesNotExist:
        log.info('User %s has already revoked the key, nothing to be done.'
                 % original_key.user)
        pass
Esempio n. 22
0
    def authenticate_credentials(self, payload):
        """
        Returns a verified AMO user who is active and allowed to make API
        requests.
        """
        if 'orig_iat' in payload:
            msg = ("API key based tokens are not refreshable, don't include "
                   '`orig_iat` in their payload.')
            raise exceptions.AuthenticationFailed(msg)
        try:
            api_key = APIKey.get_jwt_key(key=payload['iss'])
        except APIKey.DoesNotExist:
            msg = 'Invalid API Key.'
            raise exceptions.AuthenticationFailed(msg)

        if api_key.user.deleted:
            msg = 'User account is disabled.'
            raise exceptions.AuthenticationFailed(msg)
        if not api_key.user.read_dev_agreement:
            msg = 'User has not read developer agreement.'
            raise exceptions.AuthenticationFailed(msg)

        core.set_user(api_key.user)
        return api_key.user
Esempio n. 23
0
    def authenticate_credentials(self, payload):
        """
        Returns a verified AMO user who is active and allowed to make API
        requests.
        """
        if 'orig_iat' in payload:
            msg = ("API key based tokens are not refreshable, don't include "
                   "`orig_iat` in their payload.")
            raise exceptions.AuthenticationFailed(msg)
        try:
            api_key = APIKey.get_jwt_key(key=payload['iss'])
        except APIKey.DoesNotExist:
            msg = 'Invalid API Key.'
            raise exceptions.AuthenticationFailed(msg)

        if api_key.user.deleted:
            msg = 'User account is disabled.'
            raise exceptions.AuthenticationFailed(msg)
        if not api_key.user.read_dev_agreement:
            msg = 'User has not read developer agreement.'
            raise exceptions.AuthenticationFailed(msg)

        amo.set_user(api_key.user)
        return api_key.user
Esempio n. 24
0
    def handle(self, *args, **options):
        user_data = {}

        # Do quick and dirty validation if --noinput
        if not options.get('interactive', True):
            # Stolen from django's `createsuperuser` implementation.
            try:
                for field_name in self.required_fields:
                    if options.get(field_name, None):
                        field = self.UserModel._meta.get_field(field_name)
                        user_data[field_name] = field.clean(
                            options[field_name], None)
                    else:
                        raise CommandError(
                            'You must use --%s with --noinput.' % field_name)
            except exceptions.ValidationError as exc:
                raise CommandError('; '.join(exc.messages))
        else:
            user_data = {
                field_name: self.get_value(field_name)
                for field_name in self.required_fields
            }

        user = get_user_model()._default_manager.create_superuser(**user_data)

        if options.get('add_to_supercreate_group', False):
            user.read_dev_agreement = datetime.utcnow()
            user.save(update_fields=('read_dev_agreement',))

            group, _ = Group.objects.get_or_create(
                rules='Accounts:SuperCreate',
                defaults={'name': 'Account Super Creators'})
            GroupUser.objects.create(user=user, group=group)
            apikey = APIKey.new_jwt_credentials(user=user)

            self.stdout.write(json.dumps({
                'username': user.username,
                'email': user.email,
                'api-key': apikey.key,
                'api-secret': apikey.secret
            }))

        if options.get('save_api_credentials', False):
            hostname = options.get('hostname', os.environ.get(
                'PYTEST_BASE_URL', False))
            # json object for variables file
            # set hostname to stdin or env variable

            if hostname:
                credentials = {
                    'api': {
                        hostname: {
                            'username': user.username,
                            'jwt_issuer': apikey.key,
                            'jwt_secret': apikey.secret,
                        }
                    }
                }

                # write to json file
                with open(options.get('save_api_credentials'), 'w') as outfile:
                    json.dump(credentials, outfile, indent=2)
    def handle(self, *args, **options):
        user_data = {}

        # Do quick and dirty validation if --noinput
        if not options.get('interactive', True):
            # Stolen from django's `createsuperuser` implementation.
            try:
                for field_name in self.required_fields:
                    if options.get(field_name, None):
                        field = self.UserModel._meta.get_field(field_name)
                        user_data[field_name] = field.clean(
                            options[field_name], None)
                    else:
                        raise CommandError(
                            'You must use --%s with --noinput.' % field_name)
            except exceptions.ValidationError as exc:
                raise CommandError('; '.join(exc.messages))
        else:
            user_data = {
                field_name: self.get_value(field_name)
                for field_name in self.required_fields
            }

        user = get_user_model()._default_manager.create_superuser(**user_data)

        if options.get('add_to_supercreate_group', False):
            user.read_dev_agreement = datetime.utcnow()
            user.save(update_fields=('read_dev_agreement', ))

            group, _ = Group.objects.get_or_create(
                rules='Accounts:SuperCreate',
                defaults={'name': 'Account Super Creators'})
            GroupUser.objects.create(user=user, group=group)
            apikey = APIKey.new_jwt_credentials(user=user)

            self.stdout.write(
                json.dumps({
                    'username': user.username,
                    'email': user.email,
                    'api-key': apikey.key,
                    'api-secret': apikey.secret
                }))

        if options.get('save_api_credentials', False):
            hostname = options.get('hostname',
                                   os.environ.get('PYTEST_BASE_URL', False))
            # json object for variables file
            # set hostname to stdin or env variable

            if hostname:
                credentials = {
                    'api': {
                        hostname: {
                            'username': user.username,
                            'jwt_issuer': apikey.key,
                            'jwt_secret': apikey.secret,
                        }
                    }
                }

                # write to json file
                with open(options.get('save_api_credentials'), 'w') as outfile:
                    json.dump(credentials, outfile, indent=2)