1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import {
|
18 | AutomationContextAware,
|
19 | configurationValue,
|
20 | HandlerContext,
|
21 | Parameters,
|
22 | ProjectOperationCredentials,
|
23 | RemoteRepoRef,
|
24 | Secret,
|
25 | Secrets,
|
26 | } from "@atomist/automation-client";
|
27 | import { ProviderType as RepoProviderType } from "@atomist/automation-client/lib/operations/common/RepoId";
|
28 | import { CredentialsResolver } from "@atomist/sdm";
|
29 | import * as _ from "lodash";
|
30 | import {
|
31 | GitHubAppInstallationByOwner,
|
32 | ProviderType,
|
33 | ScmProviderByType,
|
34 | } from "../../typings/types";
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | type ObtainToken = (context: HandlerContext, id?: RemoteRepoRef) => Promise<string | undefined>;
|
40 |
|
41 | @Parameters()
|
42 | export class GitHubCredentialsResolver implements CredentialsResolver {
|
43 |
|
44 | @Secret(Secrets.OrgToken)
|
45 | public readonly orgToken: string;
|
46 |
|
47 | public async eventHandlerCredentials(context: HandlerContext,
|
48 | id?: RemoteRepoRef): Promise<ProjectOperationCredentials> {
|
49 | return this.credentials(
|
50 | [
|
51 | obtainTokenFromConfiguration(this),
|
52 | ObtainTokenFromIncomingMessage,
|
53 | ObtainTokenFromGitHubApp,
|
54 | ObtainTokenFromProvider,
|
55 | ],
|
56 | context,
|
57 | id);
|
58 | }
|
59 |
|
60 | public async commandHandlerCredentials(context: HandlerContext,
|
61 | id?: RemoteRepoRef): Promise<ProjectOperationCredentials> {
|
62 | return this.credentials(
|
63 | [
|
64 | ObtainTokenFromIncomingMessage,
|
65 | obtainTokenFromConfiguration(this),
|
66 | ObtainTokenFromGitHubApp,
|
67 | ObtainTokenFromProvider,
|
68 | ],
|
69 | context,
|
70 | id);
|
71 | }
|
72 |
|
73 | private async credentials(obtainTokens: ObtainToken[],
|
74 | context: HandlerContext,
|
75 | id?: RemoteRepoRef): Promise<ProjectOperationCredentials> {
|
76 |
|
77 | for (const obtainToken of obtainTokens) {
|
78 | const token = await obtainToken(context, id);
|
79 | if (hasToken(token)) {
|
80 | return { token };
|
81 | }
|
82 | }
|
83 |
|
84 | throw new Error("No GitHub token available! Please add a token to the SDM configuration at 'sdm.github.token' "
|
85 | + "or authenticate the GitHub SCM provider from the web app or CLI.");
|
86 | }
|
87 | }
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | const ObtainTokenFromIncomingMessage: ObtainToken = async ctx => {
|
93 |
|
94 | const actx: AutomationContextAware = ctx as any;
|
95 | if (!!actx.trigger && !!actx.trigger.secrets) {
|
96 | let secret = actx.trigger.secrets.find(s => s.uri === Secrets.OrgToken);
|
97 | if (secret && hasToken(secret.value)) {
|
98 | return secret.value;
|
99 | }
|
100 | secret = actx.trigger.secrets.find(s => s.uri.startsWith(Secrets.UserToken));
|
101 | if (secret && hasToken(secret.value)) {
|
102 | return secret.value;
|
103 | }
|
104 | }
|
105 | return undefined;
|
106 | };
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | const ObtainTokenFromProvider: ObtainToken = async (ctx, id) => {
|
112 |
|
113 | if (!!id && (id.providerType === RepoProviderType.github_com)) {
|
114 | const token = await fetchTokenByProviderType(ProviderType.github_com, ctx);
|
115 | if (hasToken(token)) {
|
116 | return token;
|
117 | }
|
118 | }
|
119 | return undefined;
|
120 | };
|
121 |
|
122 |
|
123 |
|
124 |
|
125 | const ObtainTokenFromGitHubApp: ObtainToken = async (ctx, id) => {
|
126 |
|
127 | if (!!id && (id.providerType === RepoProviderType.github_com)) {
|
128 | const token = await fetchTokenByGitHubAppName(id.owner, ctx);
|
129 | if (hasToken(token)) {
|
130 | return token;
|
131 | }
|
132 | }
|
133 | return undefined;
|
134 | };
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | function obtainTokenFromConfiguration(resolver: GitHubCredentialsResolver): ObtainToken {
|
140 | return async () => {
|
141 | if (hasToken(configurationValue("token", "null"))) {
|
142 | return configurationValue<string>("token");
|
143 | } else if (hasToken(configurationValue("sdm.github.token", "null"))) {
|
144 | return configurationValue<string>("sdm.github.token");
|
145 | }
|
146 | return undefined;
|
147 | };
|
148 | }
|
149 |
|
150 | function hasToken(token: string): boolean {
|
151 | if (!token) {
|
152 | return false;
|
153 |
|
154 | } else if (token === "null" || token === "undefined") {
|
155 | return false;
|
156 | }
|
157 | return true;
|
158 | }
|
159 |
|
160 | async function fetchTokenByProviderType(providerType: ProviderType,
|
161 | ctx: HandlerContext): Promise<string> {
|
162 | const provider = await ctx.graphClient.query<ScmProviderByType.Query, ScmProviderByType.Variables>({
|
163 | name: "ScmProviderByType",
|
164 | variables: {
|
165 | providerType,
|
166 | },
|
167 | });
|
168 |
|
169 | return _.get(provider, "SCMProvider[0].credential.secret");
|
170 | }
|
171 |
|
172 | async function fetchTokenByGitHubAppName(owner: string,
|
173 | ctx: HandlerContext): Promise<string> {
|
174 | const app = await ctx.graphClient.query<GitHubAppInstallationByOwner.Query, GitHubAppInstallationByOwner.Variables>({
|
175 | name: "GitHubAppInstallationByOwner",
|
176 | variables: {
|
177 | name: owner,
|
178 | },
|
179 | });
|
180 |
|
181 | return _.get(app, "GitHubAppInstallation[0].token.secret");
|
182 | }
|