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
 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)
    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
 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))
    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
Example #6
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
Example #7
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,
            }))
Example #8
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
Example #10
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
Example #11
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
Example #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
            }

        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)