From 19d996c2f3a3fe58d55d3c800253879160d7d310 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 27 Dec 2023 17:14:19 +0100 Subject: [PATCH 1/2] update community only for ADMIN and tests --- backend/src/auth/ADMIN_RIGHTS.ts | 8 +- backend/src/auth/RIGHTS.ts | 2 + backend/src/graphql/arg/CommunityArgs.ts | 14 ++ backend/src/graphql/model/Community.ts | 4 + .../resolver/CommunityResolver.test.ts | 153 +++++++++++++++++- .../src/graphql/resolver/CommunityResolver.ts | 43 ++++- .../graphql/resolver/TransactionResolver.ts | 4 +- .../src/graphql/resolver/util/communities.ts | 2 +- backend/src/seeds/graphql/mutations.ts | 16 ++ backend/src/seeds/graphql/queries.ts | 19 ++- 10 files changed, 247 insertions(+), 18 deletions(-) create mode 100644 backend/src/graphql/arg/CommunityArgs.ts diff --git a/backend/src/auth/ADMIN_RIGHTS.ts b/backend/src/auth/ADMIN_RIGHTS.ts index b81ff51d6..79006a1de 100644 --- a/backend/src/auth/ADMIN_RIGHTS.ts +++ b/backend/src/auth/ADMIN_RIGHTS.ts @@ -1,3 +1,9 @@ import { RIGHTS } from './RIGHTS' -export const ADMIN_RIGHTS = [RIGHTS.SET_USER_ROLE, RIGHTS.DELETE_USER, RIGHTS.UNDELETE_USER] +export const ADMIN_RIGHTS = [ + RIGHTS.SET_USER_ROLE, + RIGHTS.DELETE_USER, + RIGHTS.UNDELETE_USER, + RIGHTS.COMMUNITY_UPDATE, + RIGHTS.COMMUNITY_BY_UUID, +] diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index 0f6a4c00c..c95aa18fd 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -58,4 +58,6 @@ export enum RIGHTS { SET_USER_ROLE = 'SET_USER_ROLE', DELETE_USER = 'DELETE_USER', UNDELETE_USER = 'UNDELETE_USER', + COMMUNITY_BY_UUID = 'COMMUNITY_BY_UUID', + COMMUNITY_UPDATE = 'COMMUNITY_UPDATE', } diff --git a/backend/src/graphql/arg/CommunityArgs.ts b/backend/src/graphql/arg/CommunityArgs.ts new file mode 100644 index 000000000..163a6e504 --- /dev/null +++ b/backend/src/graphql/arg/CommunityArgs.ts @@ -0,0 +1,14 @@ +import { IsString } from 'class-validator' +import { Field, ArgsType, InputType } from 'type-graphql' + +@InputType() +@ArgsType() +export class CommunityArgs { + @Field(() => String) + @IsString() + uuid: string + + @Field(() => String) + @IsString() + gmsApiKey: string +} diff --git a/backend/src/graphql/model/Community.ts b/backend/src/graphql/model/Community.ts index 43e0a7108..06c07875e 100644 --- a/backend/src/graphql/model/Community.ts +++ b/backend/src/graphql/model/Community.ts @@ -12,6 +12,7 @@ export class Community { this.creationDate = dbCom.creationDate this.uuid = dbCom.communityUuid this.authenticatedAt = dbCom.authenticatedAt + this.gmsApiKey = dbCom.gmsApiKey } @Field(() => Int) @@ -37,4 +38,7 @@ export class Community { @Field(() => Date, { nullable: true }) authenticatedAt: Date | null + + @Field(() => String, { nullable: true }) + gmsApiKey: string | null } diff --git a/backend/src/graphql/resolver/CommunityResolver.test.ts b/backend/src/graphql/resolver/CommunityResolver.test.ts index 011670e87..3cdc04568 100644 --- a/backend/src/graphql/resolver/CommunityResolver.test.ts +++ b/backend/src/graphql/resolver/CommunityResolver.test.ts @@ -9,21 +9,38 @@ import { Connection } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' import { ApolloServerTestClient } from 'apollo-server-testing' +import { GraphQLError } from 'graphql/error/GraphQLError' import { cleanDB, testEnvironment } from '@test/helpers' +import { logger, i18n as localization } from '@test/testSetup' -import { getCommunities, communities } from '@/seeds/graphql/queries' +import { userFactory } from '@/seeds/factory/user' +import { login, updateHomeCommunityQuery } from '@/seeds/graphql/mutations' +import { getCommunities, communitiesQuery, getCommunityByUuidQuery } from '@/seeds/graphql/queries' +import { peterLustig } from '@/seeds/users/peter-lustig' + +import { getCommunityByUuid } from './util/communities' // to do: We need a setup for the tests that closes the connection -let query: ApolloServerTestClient['query'], con: Connection +let mutate: ApolloServerTestClient['mutate'], + query: ApolloServerTestClient['query'], + con: Connection + let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] con: Connection } +const peterLoginData = { + email: 'peter@lustig.de', + password: 'Aa12345_', + publisherId: 1234, +} + beforeAll(async () => { - testEnv = await testEnvironment() + testEnv = await testEnvironment(logger, localization) + mutate = testEnv.mutate query = testEnv.query con = testEnv.con await DbFederatedCommunity.clear() @@ -248,7 +265,7 @@ describe('CommunityResolver', () => { it('returns no community entry', async () => { // const result: Community[] = await query({ query: getCommunities }) // expect(result.length).toEqual(0) - await expect(query({ query: communities })).resolves.toMatchObject({ + await expect(query({ query: communitiesQuery })).resolves.toMatchObject({ data: { communities: [], }, @@ -275,7 +292,7 @@ describe('CommunityResolver', () => { }) it('returns 1 home-community entry', async () => { - await expect(query({ query: communities })).resolves.toMatchObject({ + await expect(query({ query: communitiesQuery })).resolves.toMatchObject({ data: { communities: [ { @@ -337,7 +354,7 @@ describe('CommunityResolver', () => { }) it('returns 2 community entries', async () => { - await expect(query({ query: communities })).resolves.toMatchObject({ + await expect(query({ query: communitiesQuery })).resolves.toMatchObject({ data: { communities: [ { @@ -377,5 +394,129 @@ describe('CommunityResolver', () => { }) }) }) + + describe('search community by uuid', () => { + let homeCom: DbCommunity | null + beforeEach(async () => { + await cleanDB() + jest.clearAllMocks() + const admin = await userFactory(testEnv, peterLustig) + // login as admin + await mutate({ mutation: login, variables: peterLoginData }) + + // HomeCommunity is still created in userFactory + homeCom = await getCommunityByUuid(admin.communityUuid) + + foreignCom1 = DbCommunity.create() + foreignCom1.foreign = true + foreignCom1.url = 'http://stage-2.gradido.net/api' + foreignCom1.publicKey = Buffer.from('publicKey-stage-2_Community') + foreignCom1.privateKey = Buffer.from('privateKey-stage-2_Community') + // foreignCom1.communityUuid = 'Stage2-Com-UUID' + // foreignCom1.authenticatedAt = new Date() + foreignCom1.name = 'Stage-2_Community-name' + foreignCom1.description = 'Stage-2_Community-description' + foreignCom1.creationDate = new Date() + await DbCommunity.insert(foreignCom1) + + foreignCom2 = DbCommunity.create() + foreignCom2.foreign = true + foreignCom2.url = 'http://stage-3.gradido.net/api' + foreignCom2.publicKey = Buffer.from('publicKey-stage-3_Community') + foreignCom2.privateKey = Buffer.from('privateKey-stage-3_Community') + foreignCom2.communityUuid = 'Stage3-Com-UUID' + foreignCom2.authenticatedAt = new Date() + foreignCom2.name = 'Stage-3_Community-name' + foreignCom2.description = 'Stage-3_Community-description' + foreignCom2.creationDate = new Date() + await DbCommunity.insert(foreignCom2) + }) + + it('finds the home-community', async () => { + await expect( + query({ + query: getCommunityByUuidQuery, + variables: { communityUuid: homeCom?.communityUuid }, + }), + ).resolves.toMatchObject({ + data: { + getCommunityByUuid: { + id: homeCom?.id, + foreign: homeCom?.foreign, + name: homeCom?.name, + description: homeCom?.description, + url: homeCom?.url, + creationDate: homeCom?.creationDate?.toISOString(), + uuid: homeCom?.communityUuid, + authenticatedAt: homeCom?.authenticatedAt, + }, + }, + }) + }) + + it('updates the home-community gmsApiKey', async () => { + await expect( + mutate({ + mutation: updateHomeCommunityQuery, + variables: { uuid: homeCom?.communityUuid, gmsApiKey: 'gmsApiKey' }, + }), + ).resolves.toMatchObject({ + data: { + updateHomeCommunity: { + id: expect.any(Number), + foreign: homeCom?.foreign, + name: homeCom?.name, + description: homeCom?.description, + url: homeCom?.url, + creationDate: homeCom?.creationDate?.toISOString(), + uuid: homeCom?.communityUuid, + authenticatedAt: homeCom?.authenticatedAt, + gmsApiKey: 'gmsApiKey', + }, + }, + }) + }) + + it('throws error on updating a foreign-community', async () => { + expect( + await mutate({ + mutation: updateHomeCommunityQuery, + variables: { uuid: foreignCom2.communityUuid, gmsApiKey: 'gmsApiKey' }, + }), + ).toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Error: Only the HomeCommunity could be modified!')], + }), + ) + }) + + it('throws error on updating a community without uuid', async () => { + expect( + await mutate({ + mutation: updateHomeCommunityQuery, + variables: { uuid: null, gmsApiKey: 'gmsApiKey' }, + }), + ).toEqual( + expect.objectContaining({ + errors: [ + new GraphQLError(`Variable "$uuid" of non-null type "String!" must not be null.`), + ], + }), + ) + }) + + it('throws error on updating a community with not existing uuid', async () => { + expect( + await mutate({ + mutation: updateHomeCommunityQuery, + variables: { uuid: 'unknownUuid', gmsApiKey: 'gmsApiKey' }, + }), + ).toEqual( + expect.objectContaining({ + errors: [new GraphQLError('HomeCommunity with uuid not found: ')], + }), + ) + }) + }) }) }) diff --git a/backend/src/graphql/resolver/CommunityResolver.ts b/backend/src/graphql/resolver/CommunityResolver.ts index 989dc74bf..dda43a34c 100644 --- a/backend/src/graphql/resolver/CommunityResolver.ts +++ b/backend/src/graphql/resolver/CommunityResolver.ts @@ -1,15 +1,16 @@ import { IsNull, Not } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' -import { Resolver, Query, Authorized, Arg } from 'type-graphql' +import { Resolver, Query, Authorized, Arg, Mutation, Args } from 'type-graphql' +import { CommunityArgs } from '@arg//CommunityArgs' import { Community } from '@model/Community' import { FederatedCommunity } from '@model/FederatedCommunity' import { RIGHTS } from '@/auth/RIGHTS' import { LogError } from '@/server/LogError' -import { getCommunity } from './util/communities' +import { getCommunityByUuid } from './util/communities' @Resolver() export class CommunityResolver { @@ -40,13 +41,41 @@ export class CommunityResolver { return dbCommunities.map((dbCom: DbCommunity) => new Community(dbCom)) } - @Authorized([RIGHTS.COMMUNITIES]) + @Authorized([RIGHTS.COMMUNITY_BY_UUID]) @Query(() => Community) - async community(@Arg('communityUuid') communityUuid: string): Promise { - const community = await getCommunity(communityUuid) - if (!community) { + async getCommunityByUuid(@Arg('communityUuid') communityUuid: string): Promise { + const com: DbCommunity | null = await getCommunityByUuid(communityUuid) + if (!com) { throw new LogError('community not found', communityUuid) } - return new Community(community) + return new Community(com) + } + + @Authorized([RIGHTS.COMMUNITY_UPDATE]) + @Mutation(() => Community) + async updateHomeCommunity(@Args() { uuid, gmsApiKey }: CommunityArgs): Promise { + let homeCom: DbCommunity | null + let com: Community + if (uuid) { + let toUpdate = false + homeCom = await getCommunityByUuid(uuid) + if (!homeCom) { + throw new LogError('HomeCommunity with uuid not found: ', uuid) + } + if (homeCom.foreign) { + throw new LogError('Error: Only the HomeCommunity could be modified!') + } + if (homeCom.gmsApiKey !== gmsApiKey) { + homeCom.gmsApiKey = gmsApiKey + toUpdate = true + } + if (toUpdate) { + await DbCommunity.save(homeCom) + } + com = new Community(homeCom) + } else { + throw new LogError(`HomeCommunity without an uuid can't be modified!`) + } + return com } } diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 8d35708a6..a763edc08 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -38,7 +38,7 @@ import { calculateBalance } from '@/util/validate' import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions' import { BalanceResolver } from './BalanceResolver' -import { getCommunity, getCommunityName, isHomeCommunity } from './util/communities' +import { getCommunityByUuid, getCommunityName, isHomeCommunity } from './util/communities' import { findUserByIdentifier } from './util/findUserByIdentifier' import { getLastTransaction } from './util/getLastTransaction' import { getTransactionList } from './util/getTransactionList' @@ -445,7 +445,7 @@ export class TransactionResolver { if (!CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED) { throw new LogError('X-Community sendCoins disabled per configuration!') } - const recipCom = await getCommunity(recipientCommunityIdentifier) + const recipCom = await getCommunityByUuid(recipientCommunityIdentifier) logger.debug('recipient commuity: ', recipCom) if (recipCom === null) { throw new LogError( diff --git a/backend/src/graphql/resolver/util/communities.ts b/backend/src/graphql/resolver/util/communities.ts index 0c0023a19..e506548c5 100644 --- a/backend/src/graphql/resolver/util/communities.ts +++ b/backend/src/graphql/resolver/util/communities.ts @@ -58,7 +58,7 @@ export async function getCommunityName(communityIdentifier: string): Promise { +export async function getCommunityByUuid(communityUuid: string): Promise { return await DbCommunity.findOne({ where: [{ communityUuid }], }) diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 981bb0da6..554cc4def 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -354,3 +354,19 @@ export const logout = gql` logout } ` + +export const updateHomeCommunityQuery = gql` + mutation ($uuid: String!, $gmsApiKey: String!) { + updateHomeCommunity(uuid: $uuid, gmsApiKey: $gmsApiKey) { + id + foreign + name + description + url + creationDate + uuid + authenticatedAt + gmsApiKey + } + } +` diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 4bd5008d3..44c3c35e1 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -118,7 +118,7 @@ export const listGDTEntriesQuery = gql` } ` -export const communities = gql` +export const communitiesQuery = gql` query { communities { id @@ -129,6 +129,23 @@ export const communities = gql` creationDate uuid authenticatedAt + gmsApiKey + } + } +` + +export const getCommunityByUuidQuery = gql` + query ($communityUuid: String!) { + getCommunityByUuid(communityUuid: $communityUuid) { + id + foreign + name + description + url + creationDate + uuid + authenticatedAt + gmsApiKey } } ` From cf9ef95b5b42f342e06ed81b68ac8bab5f1df24a Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 9 Feb 2024 22:35:53 +0100 Subject: [PATCH 2/2] rework review comments --- backend/src/graphql/resolver/CommunityResolver.test.ts | 2 +- backend/src/graphql/resolver/CommunityResolver.ts | 2 +- backend/src/seeds/graphql/queries.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/CommunityResolver.test.ts b/backend/src/graphql/resolver/CommunityResolver.test.ts index 94bf40a70..e720eb716 100644 --- a/backend/src/graphql/resolver/CommunityResolver.test.ts +++ b/backend/src/graphql/resolver/CommunityResolver.test.ts @@ -494,7 +494,7 @@ describe('CommunityResolver', () => { }), ).resolves.toMatchObject({ data: { - getCommunityByUuid: { + community: { id: homeCom?.id, foreign: homeCom?.foreign, name: homeCom?.name, diff --git a/backend/src/graphql/resolver/CommunityResolver.ts b/backend/src/graphql/resolver/CommunityResolver.ts index dda43a34c..760b982cc 100644 --- a/backend/src/graphql/resolver/CommunityResolver.ts +++ b/backend/src/graphql/resolver/CommunityResolver.ts @@ -43,7 +43,7 @@ export class CommunityResolver { @Authorized([RIGHTS.COMMUNITY_BY_UUID]) @Query(() => Community) - async getCommunityByUuid(@Arg('communityUuid') communityUuid: string): Promise { + async community(@Arg('communityUuid') communityUuid: string): Promise { const com: DbCommunity | null = await getCommunityByUuid(communityUuid) if (!com) { throw new LogError('community not found', communityUuid) diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 290d93ad6..6bd106174 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -136,7 +136,7 @@ export const communitiesQuery = gql` export const getCommunityByUuidQuery = gql` query ($communityUuid: String!) { - getCommunityByUuid(communityUuid: $communityUuid) { + community(communityUuid: $communityUuid) { id foreign name