Skip to content

Commit

Permalink
addIntegrationProvider mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
jordanh committed Nov 2, 2021
1 parent 9fb80ed commit fa3dbd8
Show file tree
Hide file tree
Showing 10 changed files with 472 additions and 8 deletions.
45 changes: 45 additions & 0 deletions packages/client/mutations/AddIntegrationProviderMutation.ts
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
92 changes: 90 additions & 2 deletions packages/client/types/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50063,7 +50063,7 @@ export interface IGitLabIntegration {
}

/**
* OAuth token for a team member
* An authentication provider configuration
*/
export interface IIntegrationProvider {
__typename: 'IntegrationProvider';
Expand Down Expand Up @@ -50124,7 +50124,7 @@ export interface IIntegrationProvider {
orgId: string;

/**
* *The team that the token is linked to
* The team that the token is linked to
*/
teamId: string;

Expand Down Expand Up @@ -55985,6 +55985,11 @@ export interface IMutation {
* Add integration token material to the team, supported by the GitLab integration
*/
addIntegrationToken: AddIntegrationTokenPayload;

/**
* Adds a new integration provider configuration
*/
addIntegrationProvider: AddIntegrationProviderPayload;
}

export interface IAcceptTeamInvitationOnMutationArguments {
Expand Down Expand Up @@ -57170,6 +57175,13 @@ export interface IAddIntegrationTokenOnMutationArguments {
redirectUri: any;
}

export interface IAddIntegrationProviderOnMutationArguments {
/**
* The new integration provider
*/
provider: IIntegrationProviderInput;
}

export interface IAcceptTeamInvitationPayload {
__typename: 'AcceptTeamInvitationPayload';
error: IStandardMutationError | null;
Expand Down Expand Up @@ -59766,6 +59778,82 @@ export interface IAddIntegrationTokenSuccess {
user: IUser | null;
}

/**
* Return object for AddIntegrationProviderPayload
*/
export type AddIntegrationProviderPayload =
| IErrorPayload
| IAddIntegrationProviderSuccess;

export interface IAddIntegrationProviderSuccess {
__typename: 'AddIntegrationProviderSuccess';

/**
* The team member with the updated auth
*/
teamMember: ITeamMember | null;

/**
* The user who updated IntegrationToken object
*/
user: IUser | null;
}

/**
* An integration provider configuration
*/
export interface IIntegrationProviderInput {
/**
* The service this provider is associated with
*/
providerType?: IntegrationProviderTypeEnum | null;

/**
* The kind of token used by this provider
*/
providerTokenType?: IntegrationProviderTokenTypeEnum | null;

/**
* The scope this provider configuration was created at (globally, org-wide, or by the team)
*/
providerScope?: IntegrationProviderScopesEnum | null;

/**
* The name of the provider, suitable for display on a user interface
*/
name: string;

/**
* A list of scope strings that should be requested from the provider
*/
scopes?: Array<string> | null;

/**
* The base URI used to access the provider
*/
serverBaseUri: any;

/**
* The client id to give to the provider
*/
oauthClientId?: string | null;

/**
* The client id to give to the provider
*/
oauthClientSecret?: string | null;

/**
* The org that the access token is attached to
*/
orgId: string;

/**
* The team that the token is linked to
*/
teamId: string;
}

export interface ISubscription {
__typename: 'Subscription';
meetingSubscription: MeetingSubscriptionPayload;
Expand Down
129 changes: 129 additions & 0 deletions packages/server/graphql/mutations/addIntegrationProvider.ts
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
4 changes: 3 additions & 1 deletion packages/server/graphql/rootMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ import verifyEmail from './mutations/verifyEmail'
import voteForPokerStory from './mutations/voteForPokerStory'
import toggleTeamDrawer from './mutations/toggleTeamDrawer'
import addIntegrationToken from './mutations/addIntegrationToken'
import addIntegrationProvider from './mutations/addIntegrationProvider'
import voteForReflectionGroup from './mutations/voteForReflectionGroup'

interface Context extends InternalContext, GQLContext {}
Expand Down Expand Up @@ -280,6 +281,7 @@ export default new GraphQLObjectType<any, Context>({
toggleTeamDrawer,
updateGitHubDimensionField,
createPoll,
addIntegrationToken
addIntegrationToken,
addIntegrationProvider
} as any)
})
63 changes: 63 additions & 0 deletions packages/server/graphql/types/AddIntegrationProviderInput.ts
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
Loading

0 comments on commit fa3dbd8

Please sign in to comment.