API - add routes to manage oauth clients

This commit is contained in:
Sam 2022-05-28 15:36:18 +02:00
parent ca9ba138b3
commit 489710c29d
9 changed files with 564 additions and 21 deletions

View File

@ -30,6 +30,7 @@ def upgrade():
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )
op.create_index(op.f('ix_oauth2_client_client_id'), 'oauth2_client', ['client_id'], unique=False) op.create_index(op.f('ix_oauth2_client_client_id'), 'oauth2_client', ['client_id'], unique=False)
op.create_index(op.f('ix_oauth2_client_user_id'), 'oauth2_client', ['user_id'], unique=False)
op.create_table('oauth2_code', op.create_table('oauth2_code',
sa.Column('code', sa.String(length=120), nullable=False), sa.Column('code', sa.String(length=120), nullable=False),
sa.Column('client_id', sa.String(length=48), nullable=True), sa.Column('client_id', sa.String(length=48), nullable=True),
@ -46,6 +47,8 @@ def upgrade():
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code') sa.UniqueConstraint('code')
) )
op.create_index('ix_oauth2_code_client_id', 'oauth2_code', ['client_id'], unique=False)
op.create_index(op.f('ix_oauth2_code_user_id'), 'oauth2_code', ['user_id'], unique=False)
op.create_table('oauth2_token', op.create_table('oauth2_token',
sa.Column('client_id', sa.String(length=48), nullable=True), sa.Column('client_id', sa.String(length=48), nullable=True),
sa.Column('token_type', sa.String(length=40), nullable=True), sa.Column('token_type', sa.String(length=40), nullable=True),
@ -63,14 +66,19 @@ def upgrade():
sa.UniqueConstraint('access_token') sa.UniqueConstraint('access_token')
) )
op.create_index(op.f('ix_oauth2_token_refresh_token'), 'oauth2_token', ['refresh_token'], unique=False) op.create_index(op.f('ix_oauth2_token_refresh_token'), 'oauth2_token', ['refresh_token'], unique=False)
op.create_index(op.f('ix_oauth2_token_user_id'), 'oauth2_token', ['user_id'], unique=False)
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade(): def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_oauth2_token_user_id'), table_name='oauth2_token')
op.drop_index(op.f('ix_oauth2_token_refresh_token'), table_name='oauth2_token') op.drop_index(op.f('ix_oauth2_token_refresh_token'), table_name='oauth2_token')
op.drop_table('oauth2_token') op.drop_table('oauth2_token')
op.drop_index(op.f('ix_oauth2_code_user_id'), table_name='oauth2_code')
op.drop_index('ix_oauth2_code_client_id', table_name='oauth2_code')
op.drop_table('oauth2_code') op.drop_table('oauth2_code')
op.drop_index(op.f('ix_oauth2_client_user_id'), table_name='oauth2_client')
op.drop_index(op.f('ix_oauth2_client_client_id'), table_name='oauth2_client') op.drop_index(op.f('ix_oauth2_client_client_id'), table_name='oauth2_client')
op.drop_table('oauth2_client') op.drop_table('oauth2_client')
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@ -38,6 +38,7 @@ def create_oauth_client(metadata: Dict, user: User) -> OAuth2Client:
""" """
client_metadata = { client_metadata = {
'client_name': metadata['client_name'], 'client_name': metadata['client_name'],
'client_description': metadata.get('client_description'),
'client_uri': metadata['client_uri'], 'client_uri': metadata['client_uri'],
'redirect_uris': metadata['redirect_uris'], 'redirect_uris': metadata['redirect_uris'],
'scope': check_scope(metadata['scope']), 'scope': check_scope(metadata['scope']),

View File

@ -1,12 +1,16 @@
import time import time
from typing import Dict from typing import Any, Dict, Optional
from authlib.integrations.sqla_oauth2 import ( from authlib.integrations.sqla_oauth2 import (
OAuth2AuthorizationCodeMixin, OAuth2AuthorizationCodeMixin,
OAuth2ClientMixin, OAuth2ClientMixin,
OAuth2TokenMixin, OAuth2TokenMixin,
) )
from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for
from sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.orm.mapper import Mapper
from sqlalchemy.orm.session import Session
from fittrackee import db from fittrackee import db
@ -18,27 +22,59 @@ class OAuth2Client(BaseModel, OAuth2ClientMixin):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
user_id = db.Column( user_id = db.Column(
db.Integer, db.ForeignKey('users.id', ondelete='CASCADE') db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), index=True
) )
user = db.relationship('User') user = db.relationship('User')
def serialize(self) -> Dict: def serialize(self, with_secret: bool = False) -> Dict:
return { client = {
'client_id': self.client_id, 'client_id': self.client_id,
'client_secret': self.client_secret, 'client_description': self.client_description,
'id': self.id, 'id': self.id,
'issued_at': time.strftime(
'%a, %d %B %Y %H:%M:%S GMT',
time.gmtime(self.client_id_issued_at),
),
'name': self.client_name, 'name': self.client_name,
'redirect_uris': self.redirect_uris, 'redirect_uris': self.redirect_uris,
'scope': self.scope,
'website': self.client_uri, 'website': self.client_uri,
} }
if with_secret:
client['client_secret'] = self.client_secret
return client
@property
def client_description(self) -> Optional[str]:
return self.client_metadata.get('client_description')
@listens_for(OAuth2Client, 'after_delete')
def on_old_oauth2_delete(
mapper: Mapper, connection: Connection, old_oauth2_client: OAuth2Client
) -> None:
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session: Session, context: Any) -> None:
session.query(OAuth2AuthorizationCode).filter(
OAuth2AuthorizationCode.client_id == old_oauth2_client.client_id
).delete(synchronize_session=False)
session.query(OAuth2Token).filter(
OAuth2Token.client_id == old_oauth2_client.client_id
).delete(synchronize_session=False)
class OAuth2AuthorizationCode(BaseModel, OAuth2AuthorizationCodeMixin): class OAuth2AuthorizationCode(BaseModel, OAuth2AuthorizationCodeMixin):
__tablename__ = 'oauth2_code' __tablename__ = 'oauth2_code'
__table_args__ = (
db.Index(
'ix_oauth2_code_client_id',
'client_id',
),
)
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
user_id = db.Column( user_id = db.Column(
db.Integer, db.ForeignKey('users.id', ondelete='CASCADE') db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), index=True
) )
user = db.relationship('User') user = db.relationship('User')
@ -48,7 +84,7 @@ class OAuth2Token(BaseModel, OAuth2TokenMixin):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
user_id = db.Column( user_id = db.Column(
db.Integer, db.ForeignKey('users.id', ondelete='CASCADE') db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), index=True
) )
user = db.relationship('User') user = db.relationship('User')

View File

@ -3,8 +3,13 @@ from typing import Dict, Tuple, Union
from flask import Blueprint, Response, request from flask import Blueprint, Response, request
from fittrackee import db from fittrackee import db
from fittrackee.oauth2.models import OAuth2Client
from fittrackee.oauth2.server import require_auth from fittrackee.oauth2.server import require_auth
from fittrackee.responses import HttpResponse, InvalidPayloadErrorResponse from fittrackee.responses import (
HttpResponse,
InvalidPayloadErrorResponse,
NotFoundErrorResponse,
)
from fittrackee.users.models import User from fittrackee.users.models import User
from .client import create_oauth_client from .client import create_oauth_client
@ -18,6 +23,36 @@ EXPECTED_METADATA_KEYS = [
'redirect_uris', 'redirect_uris',
'scope', 'scope',
] ]
DEFAULT_PER_PAGE = 5
@oauth_blueprint.route('/oauth/apps', methods=['GET'])
@require_auth()
def get_clients(auth_user: User) -> Dict:
params = request.args.copy()
page = int(params.get('page', 1))
per_page = DEFAULT_PER_PAGE
clients_pagination = (
OAuth2Client.query.filter_by(user_id=auth_user.id)
.order_by(OAuth2Client.id.desc())
.paginate(page, per_page, False)
)
clients = clients_pagination.items
return {
'status': 'success',
'data': {
'clients': [
client.serialize(with_secret=False) for client in clients
]
},
'pagination': {
'has_next': clients_pagination.has_next,
'has_prev': clients_pagination.has_prev,
'page': clients_pagination.page,
'pages': clients_pagination.pages,
'total': clients_pagination.total,
},
}
@oauth_blueprint.route('/oauth/apps', methods=['POST']) @oauth_blueprint.route('/oauth/apps', methods=['POST'])
@ -48,12 +83,47 @@ def create_client(auth_user: User) -> Union[HttpResponse, Tuple[Dict, int]]:
return ( return (
{ {
'status': 'created', 'status': 'created',
'data': {'client': new_client.serialize()}, 'data': {'client': new_client.serialize(with_secret=True)},
}, },
201, 201,
) )
@oauth_blueprint.route('/oauth/apps/<string:client_id>', methods=['GET'])
@require_auth()
def get_client(auth_user: User, client_id: str) -> Union[Dict, HttpResponse]:
client = OAuth2Client.query.filter_by(
id=client_id,
user_id=auth_user.id,
).first()
if not client:
return NotFoundErrorResponse('OAuth client not found')
return {
'status': 'success',
'data': {'client': client.serialize(with_secret=False)},
}
@oauth_blueprint.route('/oauth/apps/<string:client_id>', methods=['DELETE'])
@require_auth()
def delete_client(
auth_user: User, client_id: str
) -> Union[Tuple[Dict, int], HttpResponse]:
client = OAuth2Client.query.filter_by(
id=client_id,
user_id=auth_user.id,
).first()
if not client:
return NotFoundErrorResponse('OAuth client not found')
db.session.delete(client)
db.session.commit()
return {'status': 'no content'}, 204
@oauth_blueprint.route('/oauth/authorize', methods=['POST']) @oauth_blueprint.route('/oauth/authorize', methods=['POST'])
@require_auth() @require_auth()
def authorize(auth_user: User) -> Response: def authorize(auth_user: User) -> Response:

View File

@ -102,7 +102,7 @@ class ApiTestCaseMixin(RandomMixin):
def create_oauth_client_and_issue_token( def create_oauth_client_and_issue_token(
self, app: Flask, user: User, scope: Optional[str] = None self, app: Flask, user: User, scope: Optional[str] = None
) -> Tuple[FlaskClient, OAuth2Client, str]: ) -> Tuple[FlaskClient, OAuth2Client, str, str]:
client, auth_token = self.get_test_client_and_auth_token( client, auth_token = self.get_test_client_and_auth_token(
app, user.email app, user.email
) )
@ -121,7 +121,7 @@ class ApiTestCaseMixin(RandomMixin):
headers=dict(content_type='multipart/form-data'), headers=dict(content_type='multipart/form-data'),
) )
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
return client, oauth_client, data.get('access_token') return client, oauth_client, data.get('access_token'), auth_token
@staticmethod @staticmethod
def assert_400( def assert_400(

View File

@ -58,6 +58,26 @@ class TestCreateOAuth2Client:
assert oauth_client.client_name == client_name assert oauth_client.client_name == client_name
def test_oauth_client_has_no_description_when_not_provided_in_metadata(
self, app: Flask, user_1: User
) -> None:
oauth_client = create_oauth_client(TEST_METADATA, user_1)
assert oauth_client.client_description is None
def test_oauth_client_has_expected_description(
self, app: Flask, user_1: User
) -> None:
client_description = random_string()
client_metadata: Dict = {
**TEST_METADATA,
'client_description': client_description,
}
oauth_client = create_oauth_client(client_metadata, user_1)
assert oauth_client.client_description == client_description
def test_oauth_client_has_expected_client_uri( def test_oauth_client_has_expected_client_uri(
self, app: Flask, user_1: User self, app: Flask, user_1: User
) -> None: ) -> None:

View File

@ -7,6 +7,44 @@ from ..mixins import RandomMixin
class TestOAuthClientSerialize(RandomMixin): class TestOAuthClientSerialize(RandomMixin):
def test_it_returns_oauth_client(self, app: Flask) -> None: def test_it_returns_oauth_client(self, app: Flask) -> None:
oauth_client = OAuth2Client(
id=self.random_int(),
client_id=self.random_string(),
client_id_issued_at=1653738796,
)
oauth_client.set_client_metadata(
{
'client_name': self.random_string(),
'client_description': self.random_string(),
'redirect_uris': [self.random_string()],
'client_uri': self.random_domain(),
}
)
serialized_oauth_client = oauth_client.serialize()
assert serialized_oauth_client['client_id'] == oauth_client.client_id
assert (
serialized_oauth_client['client_description']
== oauth_client.client_description
)
assert 'client_secret' not in serialized_oauth_client
assert (
serialized_oauth_client['issued_at']
== 'Sat, 28 May 2022 11:53:16 GMT'
)
assert serialized_oauth_client['id'] == oauth_client.id
assert serialized_oauth_client['name'] == oauth_client.client_name
assert (
serialized_oauth_client['redirect_uris']
== oauth_client.redirect_uris
)
assert serialized_oauth_client['scope'] == oauth_client.scope
assert serialized_oauth_client['website'] == oauth_client.client_uri
def test_it_returns_oauth_client_with_client_secret(
self, app: Flask
) -> None:
oauth_client = OAuth2Client( oauth_client = OAuth2Client(
id=self.random_int(), id=self.random_int(),
client_id=self.random_string(), client_id=self.random_string(),
@ -20,17 +58,9 @@ class TestOAuthClientSerialize(RandomMixin):
} }
) )
serialized_oauth_client = oauth_client.serialize() serialized_oauth_client = oauth_client.serialize(with_secret=True)
assert serialized_oauth_client['client_id'] == oauth_client.client_id
assert ( assert (
serialized_oauth_client['client_secret'] serialized_oauth_client['client_secret']
== oauth_client.client_secret == oauth_client.client_secret
) )
assert serialized_oauth_client['id'] == oauth_client.id
assert serialized_oauth_client['name'] == oauth_client.client_name
assert (
serialized_oauth_client['redirect_uris']
== oauth_client.redirect_uris
)
assert serialized_oauth_client['website'] == oauth_client.client_uri

View File

@ -8,7 +8,11 @@ from flask import Flask
from urllib3.util import parse_url from urllib3.util import parse_url
from werkzeug.test import TestResponse from werkzeug.test import TestResponse
from fittrackee.oauth2.models import OAuth2Client, OAuth2Token from fittrackee.oauth2.models import (
OAuth2AuthorizationCode,
OAuth2Client,
OAuth2Token,
)
from fittrackee.users.models import User from fittrackee.users.models import User
from ..mixins import ApiTestCaseMixin from ..mixins import ApiTestCaseMixin
@ -120,6 +124,7 @@ class TestOAuthClientCreation(ApiTestCaseMixin):
data = json.loads(response.data.decode()) data = json.loads(response.data.decode())
assert data['data']['client']['client_id'] == client_id assert data['data']['client']['client_id'] == client_id
assert data['data']['client']['client_secret'] == client_secret assert data['data']['client']['client_secret'] == client_secret
assert data['data']['client']['id'] is not None
assert ( assert (
data['data']['client']['name'] data['data']['client']['name']
== TEST_OAUTH_CLIENT_METADATA['client_name'] == TEST_OAUTH_CLIENT_METADATA['client_name']
@ -222,6 +227,31 @@ class TestOAuthClientAuthorization(ApiTestCaseMixin):
self.assert_400(response, error_message='invalid payload') self.assert_400(response, error_message='invalid payload')
def test_it_creates_authorization_code(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
oauth_client = self.create_oauth_client(user_1)
client.post(
self.route,
data={
'client_id': oauth_client.client_id,
'response_type': 'code',
},
headers=dict(
Authorization=f'Bearer {auth_token}',
content_type='multipart/form-data',
),
)
code = OAuth2AuthorizationCode.query.filter_by(
client_id=oauth_client.client_id
).first()
assert code is not None
def test_it_returns_code_in_url(self, app: Flask, user_1: User) -> None: def test_it_returns_code_in_url(self, app: Flask, user_1: User) -> None:
client, auth_token = self.get_test_client_and_auth_token( client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email app, user_1.email
@ -384,6 +414,7 @@ class TestOAuthTokenRevocation(ApiTestCaseMixin):
client, client,
oauth_client, oauth_client,
access_token, access_token,
_,
) = self.create_oauth_client_and_issue_token(app, user_1) ) = self.create_oauth_client_and_issue_token(app, user_1)
response = client.post( response = client.post(
@ -401,3 +432,342 @@ class TestOAuthTokenRevocation(ApiTestCaseMixin):
client_id=oauth_client.client_id client_id=oauth_client.client_id
).first() ).first()
assert token.access_token_revoked_at is not None assert token.access_token_revoked_at is not None
class TestOAuthGetClients(ApiTestCaseMixin):
route = '/api/oauth/apps'
def test_it_returns_error_if_not_authenticated(
self, app: Flask, user_1: User
) -> None:
client = app.test_client()
response = client.get(self.route, content_type='application/json')
self.assert_401(response)
def test_it_returns_empty_list_when_no_clients(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.get(
self.route,
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert data['data']['clients'] == []
def test_it_returns_pagination(self, app: Flask, user_1: User) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
[self.create_oauth_client(user_1) for _ in range(7)]
response = client.get(
self.route,
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert len(data['data']['clients']) == 5
assert data['pagination'] == {
'has_next': True,
'has_prev': False,
'page': 1,
'pages': 2,
'total': 7,
}
def test_it_returns_page_2(self, app: Flask, user_1: User) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
[self.create_oauth_client(user_1) for _ in range(6)]
response = client.get(
f'{self.route}?page=2',
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert len(data['data']['clients']) == 1
assert data['pagination'] == {
'has_next': False,
'has_prev': True,
'page': 2,
'pages': 2,
'total': 6,
}
def test_it_returns_clients_order_by_id_descending(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
clients = [self.create_oauth_client(user_1) for _ in range(7)]
response = client.get(
self.route,
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert data['data']['clients'][0]['client_id'] == clients[6].client_id
assert data['data']['clients'][4]['client_id'] == clients[2].client_id
def test_it_does_not_returns_clients_from_another_user(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
self.create_oauth_client(user_2)
response = client.get(
self.route,
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert data['data']['clients'] == []
class TestOAuthGetClient(ApiTestCaseMixin):
route = '/api/oauth/apps/{client_id}'
def test_it_returns_error_when_not_authenticated(
self, app: Flask, user_1: User
) -> None:
client = app.test_client()
response = client.get(
self.route.format(client_id=self.random_int()),
content_type='application/json',
)
self.assert_401(response)
def test_it_returns_error_when_client_not_found(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.get(
self.route.format(client_id=self.random_int()),
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_404_with_message(response, 'OAuth client not found')
def test_it_returns_user_oauth_client(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
client_description = self.random_string()
oauth_client = self.create_oauth_client(
user_1,
metadata={
**TEST_OAUTH_CLIENT_METADATA,
'client_description': client_description,
},
)
client_id = oauth_client.id
client_client_id = oauth_client.client_id
response = client.get(
self.route.format(client_id=client_id),
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert data['data']['client']['client_id'] == client_client_id
assert 'client_secret' not in data['data']['client']
assert (
data['data']['client']['client_description'] == client_description
)
assert data['data']['client']['id'] == client_id
assert (
data['data']['client']['name']
== TEST_OAUTH_CLIENT_METADATA['client_name']
)
assert (
data['data']['client']['redirect_uris']
== TEST_OAUTH_CLIENT_METADATA['redirect_uris']
)
assert (
data['data']['client']['website']
== TEST_OAUTH_CLIENT_METADATA['client_uri']
)
def test_it_does_not_return_oauth_client_from_another_user(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
oauth_client = self.create_oauth_client(user_2)
client_id = oauth_client.id
response = client.delete(
self.route.format(client_id=client_id),
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_404_with_message(response, 'OAuth client not found')
class TestOAuthDeleteClient(ApiTestCaseMixin):
route = '/api/oauth/apps/{client_id}'
def test_it_returns_error_when_not_authenticated(
self, app: Flask, user_1: User
) -> None:
client = app.test_client()
response = client.delete(
self.route.format(client_id=self.random_int()),
content_type='application/json',
)
self.assert_401(response)
def test_it_returns_error_when_client_not_found(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.delete(
self.route.format(client_id=self.random_int()),
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_404_with_message(response, 'OAuth client not found')
def test_it_deletes_user_oauth_client(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
oauth_client = self.create_oauth_client(user_1)
client_id = oauth_client.id
response = client.delete(
self.route.format(client_id=client_id),
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
deleted_client = OAuth2Client.query.filter_by(id=client_id).first()
assert deleted_client is None
def test_it_deletes_user_authorized_oauth_client(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
oauth_client = self.create_oauth_client(user_1)
self.authorize_client(client, oauth_client, auth_token)
client_id = oauth_client.id
response = client.delete(
self.route.format(client_id=client_id),
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
deleted_client = OAuth2Client.query.filter_by(id=client_id).first()
assert deleted_client is None
def test_it_deletes_existing_code_associated_to_client(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
oauth_client = self.create_oauth_client(user_1)
code = self.authorize_client(client, oauth_client, auth_token)
client_id = oauth_client.id
response = client.delete(
self.route.format(client_id=client_id),
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
deleted_code = OAuth2AuthorizationCode.query.filter_by(
code=code[0]
).first()
assert deleted_code is None
def test_it_deletes_existing_token_associated_to_client(
self, app: Flask, user_1: User
) -> None:
(
client,
oauth_client,
access_token,
auth_token,
) = self.create_oauth_client_and_issue_token(app, user_1)
client_id = oauth_client.id
response = client.delete(
self.route.format(client_id=client_id),
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 204
token = OAuth2Token.query.filter_by(access_token=access_token).first()
assert token is None
def test_it_can_not_delete_oauth_client_from_another_user(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
oauth_client = self.create_oauth_client(user_2)
client_id = oauth_client.id
response = client.delete(
self.route.format(client_id=client_id),
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
self.assert_404_with_message(response, 'OAuth client not found')
client = OAuth2Client.query.filter_by(id=client_id).first()
assert client is not None

View File

@ -49,6 +49,7 @@ class TestOAuth2ScopesWithReadAccess(OAuth2ScopesTestCase):
client, client,
oauth_client, oauth_client,
access_token, access_token,
_,
) = self.create_oauth_client_and_issue_token( ) = self.create_oauth_client_and_issue_token(
app, user_1, scope=self.scope app, user_1, scope=self.scope
) )
@ -74,6 +75,7 @@ class TestOAuth2ScopesWithReadAccess(OAuth2ScopesTestCase):
client, client,
oauth_client, oauth_client,
access_token, access_token,
_,
) = self.create_oauth_client_and_issue_token( ) = self.create_oauth_client_and_issue_token(
app, user_1_admin, scope=self.scope app, user_1_admin, scope=self.scope
) )
@ -106,6 +108,7 @@ class TestOAuth2ScopesWithReadAccess(OAuth2ScopesTestCase):
client, client,
oauth_client, oauth_client,
access_token, access_token,
_,
) = self.create_oauth_client_and_issue_token( ) = self.create_oauth_client_and_issue_token(
app, user_1, scope=self.scope app, user_1, scope=self.scope
) )
@ -135,6 +138,7 @@ class TestOAuth2ScopesWithReadAccess(OAuth2ScopesTestCase):
client, client,
oauth_client, oauth_client,
access_token, access_token,
_,
) = self.create_oauth_client_and_issue_token( ) = self.create_oauth_client_and_issue_token(
app, user_1, scope=self.scope app, user_1, scope=self.scope
) )
@ -165,6 +169,7 @@ class TestOAuth2ScopesWithReadAccess(OAuth2ScopesTestCase):
client, client,
oauth_client, oauth_client,
access_token, access_token,
_,
) = self.create_oauth_client_and_issue_token( ) = self.create_oauth_client_and_issue_token(
app, user_1_admin, scope=self.scope app, user_1_admin, scope=self.scope
) )
@ -196,6 +201,7 @@ class TestOAuth2ScopesWithReadAccess(OAuth2ScopesTestCase):
client, client,
oauth_client, oauth_client,
access_token, access_token,
_,
) = self.create_oauth_client_and_issue_token( ) = self.create_oauth_client_and_issue_token(
app, user_1, scope=self.scope app, user_1, scope=self.scope
) )
@ -226,6 +232,7 @@ class TestOAuth2ScopesWithReadAndWriteAccess(ApiTestCaseMixin):
client, client,
oauth_client, oauth_client,
access_token, access_token,
_,
) = self.create_oauth_client_and_issue_token( ) = self.create_oauth_client_and_issue_token(
app, user_1, scope=self.scope app, user_1, scope=self.scope
) )
@ -245,6 +252,7 @@ class TestOAuth2ScopesWithReadAndWriteAccess(ApiTestCaseMixin):
client, client,
oauth_client, oauth_client,
access_token, access_token,
_,
) = self.create_oauth_client_and_issue_token( ) = self.create_oauth_client_and_issue_token(
app, user_1, scope=self.scope app, user_1, scope=self.scope
) )