diff --git a/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplication.vue b/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplication.vue index 0fe07455e815f226dcbaf2414047cc623d5aa275..73d581937e36ff8f42133c25b73a967bff2ef503 100644 --- a/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplication.vue +++ b/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplication.vue @@ -37,7 +37,7 @@ }} </v-list-item-content> <v-list-item-action> - <v-btn color="primary"> + <v-btn color="primary" @click="deleteItem(accessToken)"> {{ $t("oauth.authorized_application.revoke") }} </v-btn> </v-list-item-action> @@ -73,5 +73,10 @@ export default { required: true, }, }, + methods: { + deleteItem(item) { + this.$emit("delete-item", item); + }, + }, }; </script> diff --git a/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplications.vue b/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplications.vue index 5a6b42102c065783a6943282266abf7e28334173..bbf35c8e7f3c2f9e71479c03feb86799b3328d7f 100644 --- a/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplications.vue +++ b/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplications.vue @@ -1,10 +1,10 @@ <template> <div> <h1 class="mb-4">{{ $t("oauth.authorized_application.title") }}</h1> - <div v-if="$apollo.queries.oauth.loading"> + <div v-if="$apollo.queries.accessTokens.loading"> <v-skeleton-loader type="card"></v-skeleton-loader> </div> - <div v-else-if="oauth"> + <div v-else-if="accessTokens"> <v-card class="mb-4"> <v-card-title> {{ $t("oauth.authorized_application.subtitle") }} @@ -14,25 +14,55 @@ </v-card-text> <v-expansion-panels flat> <authorized-application - v-for="(accessToken, index) in oauth.accessTokens" + v-for="(accessToken, index) in accessTokens" :key="accessToken.id" :access-token="accessToken" + @delete-item="openDeleteDialog" /> </v-expansion-panels> </v-card> </div> + <delete-dialog + :item="deleteItem" + :gql-mutation="require('./revokeOauthToken.graphql')" + :gql-query="require('./accessTokens.graphql')" + v-model="deleteDialog" + > + <template #title> + {{ $t("oauth.authorized_application.revoke_question") }} + </template> + <template #body> + <span v-if="deleteItem">{{ deleteItem.application.name }}</span> + </template> + <template #deleteContent> + {{ $t("oauth.authorized_application.revoke") }} + </template> + </delete-dialog> </div> </template> <script> import gqlAccessTokens from "./accessTokens.graphql"; import AuthorizedApplication from "./AuthorizedApplication.vue"; +import DeleteDialog from "../generic/dialogs/DeleteDialog.vue"; export default { name: "AuthorizedApplications", - components: { AuthorizedApplication }, + components: { DeleteDialog, AuthorizedApplication }, + data() { + return { + deleteDialog: false, + deleteItem: null, + }; + }, + methods: { + openDeleteDialog(item) { + this.deleteItem = item; + this.deleteDialog = true; + }, + }, apollo: { - oauth: { + accessTokens: { query: gqlAccessTokens, }, }, diff --git a/aleksis/core/frontend/components/authorized_oauth_applications/accessTokens.graphql b/aleksis/core/frontend/components/authorized_oauth_applications/accessTokens.graphql index 3c40c63bdb89875972a03bed057466a28a7507c9..68ab08b05d479b8149b3772406a81dd0673c3169 100644 --- a/aleksis/core/frontend/components/authorized_oauth_applications/accessTokens.graphql +++ b/aleksis/core/frontend/components/authorized_oauth_applications/accessTokens.graphql @@ -1,20 +1,18 @@ { - oauth { - accessTokens { + accessTokens: oauthAccessTokens { + id + created + updated + expires + scopes { + name + description + } + application { id - created - updated - expires - scopes { - name - description - } - application { - id - name - icon { - absoluteUrl - } + name + icon { + absoluteUrl } } } diff --git a/aleksis/core/frontend/components/authorized_oauth_applications/revokeOauthToken.graphql b/aleksis/core/frontend/components/authorized_oauth_applications/revokeOauthToken.graphql new file mode 100644 index 0000000000000000000000000000000000000000..6b2f8adbde00d9e0e4f3419673324f8962266683 --- /dev/null +++ b/aleksis/core/frontend/components/authorized_oauth_applications/revokeOauthToken.graphql @@ -0,0 +1,5 @@ +mutation ($id: ID!) { + revokeOauthToken(id: $id) { + ok + } +} diff --git a/aleksis/core/frontend/messages/en.json b/aleksis/core/frontend/messages/en.json index 63af5033dd797a5c7a1191cbbf1868bf7921ba09..3a046e694bda219530c46e30d32e3d9ec1b44658 100644 --- a/aleksis/core/frontend/messages/en.json +++ b/aleksis/core/frontend/messages/en.json @@ -175,7 +175,8 @@ "valid_until": "Valid until {date}", "access_since": "Access since {date}", "has_access_to": "Has access to:", - "revoke": "Revoke Access" + "revoke": "Revoke Access", + "revoke_question": "Are you sure you want to revoke access for this application?" } }, "people": "People", diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py index 5004c0080bc14665c0e557f898dbf4ab4e1069a8..24a68978aa2a40dce7aa6fbf3546e1e5929d62cb 100644 --- a/aleksis/core/schema/__init__.py +++ b/aleksis/core/schema/__init__.py @@ -9,7 +9,15 @@ from haystack.inputs import AutoQuery from haystack.query import SearchQuerySet from haystack.utils.loading import UnifiedIndex -from ..models import CustomMenu, DynamicRoute, Notification, PDFFile, Person, TaskUserAssignment +from ..models import ( + CustomMenu, + DynamicRoute, + Notification, + OAuthAccessToken, + PDFFile, + Person, + TaskUserAssignment, +) from ..util.apps import AppConfig from ..util.core_helpers import get_allowed_object_ids, get_app_module, get_app_packages, has_person from .celery_progress import CeleryProgressFetchedMutation, CeleryProgressType @@ -19,7 +27,7 @@ from .group import GroupType # noqa from .installed_apps import AppType from .message import MessageType from .notification import MarkNotificationReadMutation, NotificationType -from .oauth import OAuthQuery +from .oauth import OAuthAccessTokenType, OAuthRevokeTokenMutation from .pdf import PDFFileType from .person import PersonMutation, PersonType from .school_term import SchoolTermType # noqa @@ -60,7 +68,7 @@ class Query(graphene.ObjectType): two_factor = graphene.Field(TwoFactorType) - oauth = graphene.Field(OAuthQuery) + oauth_access_tokens = graphene.List(OAuthAccessTokenType) def resolve_ping(root, info, payload) -> str: return payload @@ -161,8 +169,8 @@ class Query(graphene.ObjectType): return info.context.user @staticmethod - def resolve_oauth(root, info, **kwargs): - return True + def resolve_oauth_access_tokens(root, info, **kwargs): + return OAuthAccessToken.objects.filter(user=info.context.user) class Mutation(graphene.ObjectType): @@ -172,6 +180,8 @@ class Mutation(graphene.ObjectType): celery_progress_fetched = CeleryProgressFetchedMutation.Field() + revoke_oauth_token = OAuthRevokeTokenMutation.Field() + def build_global_schema(): """Build global GraphQL schema from all apps.""" diff --git a/aleksis/core/schema/base.py b/aleksis/core/schema/base.py index e3478a34ff62be4859d8d9e88a8d73322796ad82..36d4f0647bd25d56b58e14fc4b737a3893e20372 100644 --- a/aleksis/core/schema/base.py +++ b/aleksis/core/schema/base.py @@ -1,5 +1,5 @@ -from django.db.models import Model from django.core.exceptions import PermissionDenied +from django.db.models import Model import graphene from graphene_django import DjangoObjectType diff --git a/aleksis/core/schema/oauth.py b/aleksis/core/schema/oauth.py index 74efa25615446097e758f57e16b6827bc275b2cf..c51527d0bb316f2768a844e71697a0eeb8c29100 100644 --- a/aleksis/core/schema/oauth.py +++ b/aleksis/core/schema/oauth.py @@ -31,8 +31,14 @@ class OAuthAccessTokenType(DjangoObjectType): fields = ["id", "application", "expires", "created", "updated"] -class OAuthQuery(graphene.ObjectType): - access_tokens = graphene.List(OAuthAccessTokenType) +class OAuthRevokeTokenMutation(graphene.Mutation): + class Arguments: + id = graphene.ID() # noqa - def resolve_access_tokens(root, info, **kwargs): - return OAuthAccessToken.objects.filter(user=info.context.user) + ok = graphene.Boolean() + + @staticmethod + def mutate(root, info, id): # noqa + token = OAuthAccessToken.objects.get(id=id, user=info.context.user) + token.delete() + return OAuthRevokeTokenMutation(ok=True)