diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 3000feddba0ee07e93fc4d54caac0b5d8fd66ee1..bf357e17c1e38e3d3a166fe55845811eb3e0d776 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -374,6 +374,7 @@ OAUTH2_PROVIDER = { "REFRESH_TOKEN_EXPIRE_SECONDS": 3600 * 24 * 15, "AUTHORIZATION_CODE_EXPIRE_SECONDS": 5 * 60, "ACCESS_TOKEN_EXPIRE_SECONDS": 60 * 60 * 10, + "OAUTH2_SERVER_CLASS": "funkwhale_api.users.oauth.server.OAuth2Server", } OAUTH2_PROVIDER_APPLICATION_MODEL = "users.Application" OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "users.AccessToken" diff --git a/api/funkwhale_api/users/oauth/server.py b/api/funkwhale_api/users/oauth/server.py new file mode 100644 index 0000000000000000000000000000000000000000..f62ebf48a93c438e1f762dc95c2b2d0e85013111 --- /dev/null +++ b/api/funkwhale_api/users/oauth/server.py @@ -0,0 +1,25 @@ +import urllib.parse +import oauthlib.oauth2 + + +class OAuth2Server(oauthlib.oauth2.Server): + def verify_request(self, uri, *args, **kwargs): + valid, request = super().verify_request(uri, *args, **kwargs) + if valid: + return valid, request + + # maybe the token was given in the querystring? + query = urllib.parse.urlparse(request.uri).query + token = None + if query: + parsed_qs = urllib.parse.parse_qs(query) + token = parsed_qs.get("token", []) + if len(token) > 0: + token = token[0] + + if token: + valid = self.request_validator.validate_bearer_token( + token, request.scopes, request + ) + + return valid, request diff --git a/api/tests/test_auth.py b/api/tests/test_auth.py new file mode 100644 index 0000000000000000000000000000000000000000..653110f0944e0b08e13bf40f97460de4d47a83da --- /dev/null +++ b/api/tests/test_auth.py @@ -0,0 +1,36 @@ +from django.urls import reverse +from rest_framework_jwt.settings import api_settings + +jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER +jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER + + +def test_can_authenticate_using_jwt_token_param_in_url(factories, preferences, client): + user = factories["users.User"]() + preferences["common__api_authentication_required"] = True + url = reverse("api:v1:tracks-list") + response = client.get(url) + + assert response.status_code == 401 + + payload = jwt_payload_handler(user) + token = jwt_encode_handler(payload) + response = client.get(url, data={"jwt": token}) + assert response.status_code == 200 + + +def test_can_authenticate_using_oauth_token_param_in_url( + factories, preferences, client, mocker +): + mocker.patch( + "funkwhale_api.users.oauth.permissions.should_allow", return_value=True + ) + token = factories["users.AccessToken"]() + preferences["common__api_authentication_required"] = True + url = reverse("api:v1:tracks-list") + response = client.get(url) + + assert response.status_code == 401 + + response = client.get(url, data={"token": token.token}) + assert response.status_code == 200 diff --git a/api/tests/test_jwt_querystring.py b/api/tests/test_jwt_querystring.py deleted file mode 100644 index 18a673fb480d71fdea41dc60557dfd56b6c34aaa..0000000000000000000000000000000000000000 --- a/api/tests/test_jwt_querystring.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.urls import reverse -from rest_framework_jwt.settings import api_settings - -jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER -jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER - - -def test_can_authenticate_using_token_param_in_url(factories, preferences, client): - user = factories["users.User"]() - preferences["common__api_authentication_required"] = True - url = reverse("api:v1:tracks-list") - response = client.get(url) - - assert response.status_code == 401 - - payload = jwt_payload_handler(user) - token = jwt_encode_handler(payload) - response = client.get(url, data={"jwt": token}) - assert response.status_code == 200 diff --git a/front/src/components/auth/ApplicationForm.vue b/front/src/components/auth/ApplicationForm.vue index 5bd7d3b504ae623058a2da39b7f01e0a85206bed..c2eefbfdfb705643aad0bf728e7f8764a7d3a6af 100644 --- a/front/src/components/auth/ApplicationForm.vue +++ b/front/src/components/auth/ApplicationForm.vue @@ -77,17 +77,19 @@ import TranslationsMixin from "@/components/mixins/Translations" export default { mixins: [TranslationsMixin], props: { - app: {type: Object, required: false} + app: {type: Object, required: false}, + defaults: {type: Object, required: false} }, data() { let app = this.app || {} + let defaults = this.defaults || {} return { isLoading: false, errors: [], fields: { - name: app.name || '', - redirect_uris: app.redirect_uris || 'urn:ietf:wg:oauth:2.0:oob', - scopes: app.scopes || 'read' + name: app.name || defaults.name || '', + redirect_uris: app.redirect_uris || defaults.redirect_uris || 'urn:ietf:wg:oauth:2.0:oob', + scopes: app.scopes || defaults.scopes || 'read' }, scopes: [ {id: "profile", icon: 'user'}, diff --git a/front/src/components/auth/ApplicationNew.vue b/front/src/components/auth/ApplicationNew.vue index 8bb36826ce9bef3931f0091b50e5213cbe108124..ba1a575fe79a95ae72d4c6f4611986bbc9efe9cb 100644 --- a/front/src/components/auth/ApplicationNew.vue +++ b/front/src/components/auth/ApplicationNew.vue @@ -9,6 +9,7 @@ <translate translate-context="Content/Applications/Title">Create a new application</translate> </h2> <application-form + :defaults="defaults" @created="$router.push({name: 'settings.applications.edit', params: {id: $event.client_id}})" /> </section> </div> @@ -19,6 +20,7 @@ import ApplicationForm from "@/components/auth/ApplicationForm" export default { + props: ['name', 'redirect_uris', 'scopes'], components: { ApplicationForm }, @@ -26,6 +28,11 @@ export default { return { application: null, isLoading: false, + defaults: { + name: this.name, + redirect_uris: this.redirect_uris, + scopes: this.scopes, + } } }, computed: { diff --git a/front/src/router/index.js b/front/src/router/index.js index 7b97cbe513edde232df3756418fd783225a04db1..182cf61b0714f6f2feecc529526b314d475ccab3 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -104,6 +104,11 @@ export default new Router({ { path: '/settings/applications/new', name: 'settings.applications.new', + props: (route) => ({ + scopes: route.query.scopes, + name: route.query.name, + redirect_uris: route.query.redirect_uris, + }), component: () => import(/* webpackChunkName: "core" */ "@/components/auth/ApplicationNew"), },