Merge pull request #17157 from cmdkeen/cmdkeen/stackoverflow-pat
Stack Overflow plugin support for PAT authentication
This commit is contained in:
@@ -17,9 +17,9 @@ stackoverflow:
|
||||
|
||||
### Stack Overflow for Teams
|
||||
|
||||
If you have a private Stack Overflow instance and/or a private Stack Overflow Team you will need to supply an API key. You can read more about how to set this up by going to [Stack Overflow's Help Page](https://stackoverflow.help/en/articles/4385859-stack-overflow-for-teams-api).
|
||||
If you have a private Stack Overflow instance and/or a private Stack Overflow Team you will need to supply an API key or Personal Access Token. You can read more about how to set this up by going to [Stack Overflow's Help Page](https://stackoverflow.help/en/articles/4385859-stack-overflow-for-teams-api).
|
||||
|
||||
A private Stack Overflow Team requires both an API key and an API Access Token. Step 3 ("Generate an Access Token via OAuth") from the previously mentioned Help Page explains the process for generating an API Access Token with no expiration.
|
||||
The existing API key approach remains the default, to support the new v2.3 API and PAT authentication model you need to pass the team name and the new PAT into the existing apiAccessToken parameter to the new URL. See [15770](https://github.com/backstage/backstage/issues/15770) for more details.
|
||||
|
||||
```yaml
|
||||
stackoverflow:
|
||||
@@ -28,6 +28,13 @@ stackoverflow:
|
||||
apiAccessToken: $STACK_OVERFLOW_API_ACCESS_TOKEN
|
||||
```
|
||||
|
||||
```yaml
|
||||
stackoverflow:
|
||||
baseUrl: https://api.stackoverflowteams.com/2.3 # alternative: your internal stack overflow instance
|
||||
teamName: $STACK_OVERFLOW_TEAM_NAME
|
||||
apiAccessToken: $STACK_OVERFLOW_API_ACCESS_TOKEN
|
||||
```
|
||||
|
||||
## Areas of Responsibility
|
||||
|
||||
This stack overflow backend plugin is primarily responsible for the following:
|
||||
|
||||
@@ -44,6 +44,7 @@ export type StackOverflowQuestionsCollatorFactoryOptions = {
|
||||
maxPage?: number;
|
||||
apiKey?: string;
|
||||
apiAccessToken?: string;
|
||||
teamName?: string;
|
||||
requestParams: StackOverflowQuestionsRequestParams;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
+5
@@ -30,6 +30,11 @@ export interface Config {
|
||||
*/
|
||||
apiKey?: string;
|
||||
|
||||
/**
|
||||
* The name of the team for a Stack Overflow for Teams account
|
||||
*/
|
||||
teamName?: string;
|
||||
|
||||
/**
|
||||
* The API Access Token to authenticate to Stack Overflow API
|
||||
* @visibility secret
|
||||
|
||||
+44
@@ -139,5 +139,49 @@ describe('StackOverflowQuestionsCollatorFactory', () => {
|
||||
|
||||
expect(documents).toHaveLength(mockOverrideQuestion.items.length);
|
||||
});
|
||||
|
||||
it('uses API key when provided', async () => {
|
||||
worker.use(
|
||||
rest.get('http://stack.overflow.override/questions', (req, res, ctx) =>
|
||||
req.url.searchParams.get('key') === 'abcdefg'
|
||||
? res(ctx.status(200), ctx.json(mockOverrideQuestion))
|
||||
: res(ctx.status(401), ctx.json({})),
|
||||
),
|
||||
);
|
||||
const factory = StackOverflowQuestionsCollatorFactory.fromConfig(config, {
|
||||
logger,
|
||||
baseUrl: 'http://stack.overflow.override',
|
||||
apiKey: 'abcdefg',
|
||||
requestParams: defaultOptions.requestParams,
|
||||
});
|
||||
const collator = await factory.getCollator();
|
||||
|
||||
const pipeline = TestPipeline.fromCollator(collator);
|
||||
const { documents } = await pipeline.execute();
|
||||
|
||||
expect(documents).toHaveLength(mockOverrideQuestion.items.length);
|
||||
});
|
||||
|
||||
it('uses teamName when provided', async () => {
|
||||
worker.use(
|
||||
rest.get('http://stack.overflow.override/questions', (req, res, ctx) =>
|
||||
req.url.searchParams.get('team') === 'abcdefg'
|
||||
? res(ctx.status(200), ctx.json(mockOverrideQuestion))
|
||||
: res(ctx.status(401), ctx.json({})),
|
||||
),
|
||||
);
|
||||
const factory = StackOverflowQuestionsCollatorFactory.fromConfig(config, {
|
||||
logger,
|
||||
baseUrl: 'http://stack.overflow.override',
|
||||
teamName: 'abcdefg',
|
||||
requestParams: defaultOptions.requestParams,
|
||||
});
|
||||
const collator = await factory.getCollator();
|
||||
|
||||
const pipeline = TestPipeline.fromCollator(collator);
|
||||
const { documents } = await pipeline.execute();
|
||||
|
||||
expect(documents).toHaveLength(mockOverrideQuestion.items.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,6 +53,7 @@ export type StackOverflowQuestionsCollatorFactoryOptions = {
|
||||
maxPage?: number;
|
||||
apiKey?: string;
|
||||
apiAccessToken?: string;
|
||||
teamName?: string;
|
||||
requestParams: StackOverflowQuestionsRequestParams;
|
||||
logger: Logger;
|
||||
};
|
||||
@@ -69,6 +70,7 @@ export class StackOverflowQuestionsCollatorFactory
|
||||
private readonly baseUrl: string | undefined;
|
||||
private readonly apiKey: string | undefined;
|
||||
private readonly apiAccessToken: string | undefined;
|
||||
private readonly teamName: string | undefined;
|
||||
private readonly maxPage: number | undefined;
|
||||
private readonly logger: Logger;
|
||||
public readonly type: string = 'stack-overflow';
|
||||
@@ -77,6 +79,7 @@ export class StackOverflowQuestionsCollatorFactory
|
||||
this.baseUrl = options.baseUrl;
|
||||
this.apiKey = options.apiKey;
|
||||
this.apiAccessToken = options.apiAccessToken;
|
||||
this.teamName = options.teamName;
|
||||
this.maxPage = options.maxPage;
|
||||
this.requestParams = options.requestParams;
|
||||
this.logger = options.logger.child({ documentType: this.type });
|
||||
@@ -90,15 +93,17 @@ export class StackOverflowQuestionsCollatorFactory
|
||||
const apiAccessToken = config.getOptionalString(
|
||||
'stackoverflow.apiAccessToken',
|
||||
);
|
||||
const teamName = config.getOptionalString('stackoverflow.teamName');
|
||||
const baseUrl =
|
||||
config.getOptionalString('stackoverflow.baseUrl') ||
|
||||
'https://api.stackexchange.com/2.2';
|
||||
'https://api.stackexchange.com/2.3';
|
||||
const maxPage = options.maxPage || 100;
|
||||
return new StackOverflowQuestionsCollatorFactory({
|
||||
baseUrl,
|
||||
maxPage,
|
||||
apiKey,
|
||||
apiAccessToken,
|
||||
teamName,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
@@ -114,10 +119,16 @@ export class StackOverflowQuestionsCollatorFactory
|
||||
);
|
||||
}
|
||||
|
||||
if (this.apiKey && this.teamName) {
|
||||
this.logger.debug(
|
||||
'Both stackoverflow.apiKey and stackoverflow.teamName configured in your app-config.yaml, apiKey must be removed before teamName will be used',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (Object.keys(this.requestParams).indexOf('key') >= 0) {
|
||||
this.logger.warn(
|
||||
'The API Key should be passed as a seperate param to bypass encoding',
|
||||
'The API Key should be passed as a separate param to bypass encoding',
|
||||
);
|
||||
delete this.requestParams.key;
|
||||
}
|
||||
@@ -134,6 +145,15 @@ export class StackOverflowQuestionsCollatorFactory
|
||||
? `${params ? '&' : '?'}key=${this.apiKey}`
|
||||
: '';
|
||||
|
||||
const teamParam = this.teamName
|
||||
? `${params ? '&' : '?'}team=${this.teamName}`
|
||||
: '';
|
||||
|
||||
// PAT change requires team name as a parameter
|
||||
const requestUrl = this.apiKey
|
||||
? `${this.baseUrl}/questions${params}${apiKeyParam}`
|
||||
: `${this.baseUrl}/questions${params}${teamParam}`;
|
||||
|
||||
let hasMorePages = true;
|
||||
let page = 1;
|
||||
while (hasMorePages) {
|
||||
@@ -144,7 +164,7 @@ export class StackOverflowQuestionsCollatorFactory
|
||||
break;
|
||||
}
|
||||
const res = await fetch(
|
||||
`${this.baseUrl}/questions${params}${apiKeyParam}&page=${page}`,
|
||||
`${requestUrl}&page=${page}`,
|
||||
this.apiAccessToken
|
||||
? {
|
||||
headers: {
|
||||
|
||||
Reference in New Issue
Block a user