-
Notifications
You must be signed in to change notification settings - Fork 336
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
472 additions
and
8 deletions.
There are no files selected for viewing
45 changes: 45 additions & 0 deletions
45
packages/client/mutations/AddIntegrationProviderMutation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import graphql from 'babel-plugin-relay/macro' | ||
import {commitMutation} from 'react-relay' | ||
import {StandardMutation} from '../types/relayMutations' | ||
|
||
/* TODO: Implment this when we update our Organization administration interface */ | ||
|
||
// import {AddIntegrationProviderMutation as TAddIntegrationProviderMutation} from '../__generated__/AddIntegrationProviderMutation.graphql' | ||
|
||
// graphql` | ||
// fragment AddIntegrationProviderMutation_part on AddIntegrationProviderSuccess { | ||
|
||
// } | ||
// ` | ||
|
||
// const mutation = graphql` | ||
// mutation AddIntegrationProviderMutation() { | ||
// addIntegrationProvider() { | ||
// ... on ErrorPayload { | ||
// error { | ||
// message | ||
// } | ||
// } | ||
// ...AddIntegrationProviderMutation_part @relay(mask: false) | ||
// } | ||
// } | ||
// ` | ||
|
||
// const AddIntegrationProviderMutation: StandardMutation<TAddIntegrationProviderMutation> = ( | ||
// atmosphere, | ||
// variables, | ||
// {onError, onCompleted} | ||
// ) => { | ||
// return commitMutation<TAddIntegrationProviderMutation>(atmosphere, { | ||
// mutation, | ||
// variables, | ||
// optimisticUpdater: (store) => { | ||
// const { } = variables | ||
|
||
// }, | ||
// onCompleted, | ||
// onError | ||
// }) | ||
// } | ||
|
||
// export default AddIntegrationProviderMutation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
packages/server/graphql/mutations/addIntegrationProvider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import {GraphQLNonNull} from 'graphql' | ||
import AddIntegrationProviderPayload from '../types/AddIntegrationProviderPayload' | ||
import {GQLContext} from '../graphql' | ||
import { | ||
getUserId, | ||
isSuperUser as checkSuperUser, | ||
isTeamMember as checkTeamMember, | ||
isUserBillingLeader as checkBillingLeader | ||
} from '../../utils/authorization' | ||
import standardError from '../../utils/standardError' | ||
import {SubscriptionChannel} from 'parabol-client/types/constEnums' | ||
import publish from '../../utils/publish' | ||
import AddIntegrationProviderInput from '../types/AddIntegrationProviderInput' | ||
import { | ||
IntegrationProvidersEnum as IntegrationProvidersEnumT, | ||
IntegrationProviderScopesEnum as IntegrationProviderScopesEnumT, | ||
IntegrationProviderTokenTypeEnum as IntegrationProviderTokenTypeEnumT | ||
} from '../../types/IntegrationProviderAndTokenT' | ||
import upsertGlobalIntegrationProvider from '../../postgres/queries/upsertGlobalIntegrationProvider' | ||
import insertIntegrationProvider from '../../postgres/queries/insertIntegrationProvider' | ||
|
||
type AddIntegrationProviderVariables = { | ||
provider: { | ||
providerType: Lowercase<IntegrationProvidersEnumT> | ||
providerTokenType: Lowercase<IntegrationProviderTokenTypeEnumT> | ||
providerScope: Lowercase<IntegrationProviderScopesEnumT> | ||
name: string | ||
scopes: string[] | null | ||
serverBaseUri: string | ||
oauthClientId: string | null | ||
oauthClientSecret: string | null | ||
orgId: string | ||
teamId: string | ||
} | ||
} | ||
|
||
const addIntegrationProvider = { | ||
name: 'AddIntegrationProvier', | ||
type: GraphQLNonNull(AddIntegrationProviderPayload), | ||
description: 'Adds a new integration provider configuration', | ||
args: { | ||
provider: { | ||
type: GraphQLNonNull(AddIntegrationProviderInput), | ||
description: 'The new integration provider' | ||
} | ||
}, | ||
resolve: async (_source, {provider}: AddIntegrationProviderVariables, context: GQLContext) => { | ||
const {authToken, dataLoader, socketId: mutatorId} = context | ||
const viewerId = getUserId(authToken) | ||
const {teamId, orgId} = provider | ||
const operationId = dataLoader.share() | ||
const subOptions = {mutatorId, operationId} | ||
|
||
//AUTH | ||
const isSuperUser = checkSuperUser(authToken) | ||
const isTeamMember = checkTeamMember(authToken, teamId) | ||
const isBillingLeader = checkBillingLeader(viewerId, orgId, dataLoader) | ||
|
||
switch (provider.providerScope) { | ||
case 'global': | ||
if (!isSuperUser) | ||
return standardError(new Error('permission denied adding globally scoped provider')) | ||
break | ||
case 'org': | ||
if (!isBillingLeader && !isSuperUser) | ||
return standardError( | ||
new Error('permission denied adding org-wide provider; must be billing leader') | ||
) | ||
break | ||
case 'team': | ||
if (!isTeamMember && !isBillingLeader && !isSuperUser) | ||
return standardError( | ||
new Error('persmission denied adding team provider; must be team member') | ||
) | ||
} | ||
|
||
// VALIDATION | ||
switch (provider.providerScope) { | ||
case 'global': | ||
if (provider.providerTokenType !== 'oauth2') | ||
return standardError(new Error('globally-scoped token provider must be OAuth2 provider')) | ||
break | ||
// @ts-ignore fall-through is desired here | ||
case 'org': | ||
if (provider.providerTokenType !== 'oauth2') | ||
return standardError(new Error('org-scoped token provider must be OAuth2 provider')) | ||
case 'team': | ||
const checkTeam = await dataLoader.get('teams').load(teamId) | ||
if (!checkTeam) return standardError(new Error('team not found')) | ||
const checkOrg = await dataLoader.get('organizations').load(orgId) | ||
if (!checkOrg) return standardError(new Error('organization not found')) | ||
} | ||
switch (provider.providerTokenType) { | ||
case 'oauth2': | ||
if (!provider.scopes) return standardError(new Error('scopes required for OAuth2 provider')) | ||
if (!provider.oauthClientId) | ||
return standardError(new Error('oauthClientId required for OAuth2 provider')) | ||
if (!provider.oauthClientSecret) | ||
return standardError(new Error('oauthClientSecret required for OAuth2 provider')) | ||
break | ||
case 'pat': | ||
// nothing to validate | ||
break | ||
} | ||
|
||
// RESOLUTION | ||
const dbProvider = { | ||
...provider, | ||
providerType: provider.providerType.toUpperCase() as IntegrationProvidersEnumT, | ||
providerScope: provider.providerScope.toUpperCase() as IntegrationProviderScopesEnumT, | ||
providerTokenType: provider.providerTokenType.toUpperCase() as IntegrationProviderTokenTypeEnumT, | ||
isActive: true | ||
} | ||
switch (provider.providerScope) { | ||
case 'global': | ||
upsertGlobalIntegrationProvider(dbProvider) | ||
break | ||
case 'org': | ||
case 'team': | ||
insertIntegrationProvider(dbProvider) | ||
} | ||
|
||
const data = {userId: viewerId, teamId} | ||
publish(SubscriptionChannel.TEAM, teamId, 'AddIntegrationProvider', data, subOptions) | ||
return data | ||
} | ||
} | ||
|
||
export default addIntegrationProvider |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
packages/server/graphql/types/AddIntegrationProviderInput.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { | ||
GraphQLID, | ||
GraphQLList, | ||
GraphQLNonNull, | ||
GraphQLInputObjectType, | ||
GraphQLString | ||
} from 'graphql' | ||
import GraphQLURLType from './GraphQLURLType' | ||
import { | ||
IntegrationProviderTypeEnum, | ||
IntegrationProviderTokenTypeEnum, | ||
IntegrationProviderScopesEnum | ||
} from './IntegrationProvider' | ||
|
||
const IntegrationProvider = new GraphQLInputObjectType({ | ||
name: 'IntegrationProviderInput', | ||
description: 'An integration provider configuration', | ||
fields: () => ({ | ||
providerType: { | ||
description: 'The service this provider is associated with', | ||
type: IntegrationProviderTypeEnum | ||
}, | ||
providerTokenType: { | ||
description: 'The kind of token used by this provider', | ||
type: IntegrationProviderTokenTypeEnum | ||
}, | ||
providerScope: { | ||
description: | ||
'The scope this provider configuration was created at (globally, org-wide, or by the team)', | ||
type: IntegrationProviderScopesEnum | ||
}, | ||
name: { | ||
type: new GraphQLNonNull(GraphQLString), | ||
description: 'The name of the provider, suitable for display on a user interface' | ||
}, | ||
scopes: { | ||
type: new GraphQLList(GraphQLNonNull(GraphQLString)), | ||
description: 'A list of scope strings that should be requested from the provider' | ||
}, | ||
serverBaseUri: { | ||
type: new GraphQLNonNull(GraphQLURLType), | ||
description: 'The base URI used to access the provider' | ||
}, | ||
oauthClientId: { | ||
type: GraphQLString, | ||
description: 'The client id to give to the provider' | ||
}, | ||
oauthClientSecret: { | ||
type: GraphQLString, | ||
description: 'The client id to give to the provider' | ||
}, | ||
orgId: { | ||
type: new GraphQLNonNull(GraphQLID), | ||
description: 'The org that the access token is attached to' | ||
}, | ||
teamId: { | ||
type: new GraphQLNonNull(GraphQLID), | ||
description: 'The team that the token is linked to' | ||
} | ||
}) | ||
}) | ||
|
||
export default IntegrationProvider |
Oops, something went wrong.