Skip to content

Commit 9cfee75

Browse files
authored
Fix security mapping #6417 (#6420)
1 parent f635d94 commit 9cfee75

File tree

6 files changed

+231
-22
lines changed

6 files changed

+231
-22
lines changed

src/kiota/Rpc/PathItem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ public record PathItem(
1111
string? description = null,
1212
Uri? documentationUrl = null,
1313
IEnumerable<string>? servers = null,
14-
IDictionary<string, IList<string>>? security = null, // key is the security scheme name, value is the list of scopes
14+
IList<IDictionary<string, IList<string>?>>? security = null, // key is the security scheme name, value is the list of scopes
1515
AdaptiveCardInfo? adaptiveCard = null
1616
);

src/kiota/Rpc/SecurityRequirementMapper.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ namespace kiota.Rpc
44
{
55
internal class SecurityRequirementMapper
66
{
7-
public static IDictionary<string, IList<string>>? FromSecurityRequirementList(IList<OpenApiSecurityRequirement>? securityRequirementList)
7+
public static IList<IDictionary<string, IList<string>?>>? FromSecurityRequirementList(IList<OpenApiSecurityRequirement>? securityRequirementList)
88
{
99
if (securityRequirementList is null) return null;
10-
var requirements = new Dictionary<string, IList<string>>();
10+
var requirementsList = new List<IDictionary<string, IList<string>?>>();
1111

1212
foreach (var securityRequirement in securityRequirementList)
1313
{
1414
if (securityRequirement is null) continue;
15+
var requirements = new Dictionary<string, IList<string>?>();
1516

1617
foreach (var securityRequirementItem in securityRequirement)
1718
{
@@ -25,9 +26,10 @@ internal class SecurityRequirementMapper
2526

2627
requirements.Add(name, scopes);
2728
}
28-
}
29+
requirementsList.Add(requirements);
2930

30-
return requirements;
31+
}
32+
return requirementsList;
3133
}
3234
}
3335
}

src/kiota/Rpc/SecuritySchemeMapper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ internal static Dictionary<string, SecuritySchemeInfo> FromComponents(OpenApiCom
2929
private static SecuritySchemeInfo BuildSchemeInfoFromSecurityScheme(IOpenApiSecurityScheme value)
3030
{
3131
string? description = value?.Description;
32-
string? @in = value?.In?.GetDisplayName() ?? "testvalue";
32+
string? @in = value?.In?.GetDisplayName();
3333
string? scheme = value?.Scheme;
3434
string? name = value?.Name;
3535
string? bearerFormat = value?.BearerFormat;

src/kiota/Rpc/ShowResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ public record ShowResult(
55
PathItem? rootNode,
66
string? apiTitle,
77
IEnumerable<string>? servers = null,
8-
IDictionary<string, IList<string>>? security = null,
8+
IList<IDictionary<string, IList<string>?>>? security = null,
99
IDictionary<string, SecuritySchemeInfo>? securitySchemes = null);
1010

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Repair Service
4+
version: 1.0.0
5+
servers:
6+
- url: https://sample.server/api
7+
components:
8+
securitySchemes:
9+
oAuth2AuthCode:
10+
type: oauth2
11+
description: OAuth configuration for the repair service
12+
flows:
13+
authorizationCode:
14+
authorizationUrl: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
15+
tokenUrl: https://login.microsoftonline.com/common/oauth2/v2.0/token
16+
refreshUrl: https://login.microsoftonline.com/common/oauth2/v2.0/refresh
17+
scopes:
18+
api://sample/repairs_read: Read repair records
19+
api://sample/repairs_write: Write repair records
20+
password:
21+
tokenUrl: https://login.microsoftonline.com/common/oauth2/v2.0/token
22+
refreshUrl: https://login.microsoftonline.com/common/oauth2/v2.0/refresh
23+
scopes:
24+
api://sample/repairs_read: Read repair records
25+
api://sample/repairs_write: Write repair records
26+
implicit:
27+
authorizationUrl: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
28+
refreshUrl: https://login.microsoftonline.com/common/oauth2/v2.0/refresh
29+
scopes:
30+
api://sample/repairs_read: Read repair records
31+
api://sample/repairs_write: Write repair records
32+
clientCredentials:
33+
tokenUrl: https://login.microsoftonline.com/common/oauth2/v2.0/token
34+
refreshUrl: https://login.microsoftonline.com/common/oauth2/v2.0/refresh
35+
scopes:
36+
api://sample/repairs_read: Read repair records
37+
api://sample/repairs_write: Write repair records
38+
httpAuth:
39+
type: http
40+
scheme: basic
41+
description: HTTP basic authentication
42+
apiKeyAuth:
43+
type: apiKey
44+
in: header
45+
name: X-API-Key
46+
description: API key authentication
47+
openIdConnectAuth:
48+
type: openIdConnect
49+
description: OpenID Connect authentication
50+
openIdConnectUrl: https://login.microsoftonline.com/common/.well-known/openid-configuration
51+
paths:
52+
/repairs:
53+
get:
54+
operationId: listRepairs
55+
summary: List all repairs with oauth
56+
description: Returns a list of repairs with their details and images
57+
servers:
58+
- url: https://sample.server.overridden/api
59+
security:
60+
- oAuth2AuthCode: ["api://sample/repairs_read"]
61+
responses:
62+
"200":
63+
description: A list of repairs
64+
content:
65+
application/json:
66+
schema:
67+
type: object
68+
post:
69+
summary: Create new repair with oauth
70+
description: Returns the create repair
71+
security:
72+
- oAuth2AuthCode: ["api://sample/repairs_write"]
73+
- httpAuth: []
74+
responses:
75+
"200":
76+
description: A new repair
77+
content:
78+
application/json:
79+
schema:
80+
type: object
81+
put:
82+
summary: Update new repair with oauth
83+
description: Returns the updated repair
84+
security:
85+
- oAuth2AuthCode: ["api://sample/repairs_write"]
86+
httpAuth: []
87+
responses:
88+
"200":
89+
description: A new repair
90+
content:
91+
application/json:
92+
schema:
93+
type: object

vscode/npm-package/tests/integration/integrationGetKiotaTree.spec.ts

Lines changed: 129 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { KiotaLogEntry, KiotaTreeResult, KiotaOpenApiNode, LogLevel, SecurityRequirementObject, OAuth2SecurityScheme, HttpSecurityScheme, ApiKeySecurityScheme, OpenIdSecurityScheme } from "../../types";
22
import { getKiotaTree } from "../../lib/getKiotaTree";
3-
import { LogLevel } from "../../types";
43

54
function existsGreaterThanLevelLogs(logs: KiotaLogEntry[] | undefined, level: LogLevel): boolean {
65
if (!logs) return false;
@@ -22,12 +21,6 @@ function flattenKiotaTree(tree: KiotaOpenApiNode): KiotaOpenApiNode[] {
2221
return result;
2322
}
2423

25-
function findSecurityRequirementByName(node: KiotaOpenApiNode | undefined, security_requirement_name: string): SecurityRequirementObject | undefined {
26-
if (!node) return undefined;
27-
if (!node.security) return undefined;
28-
return node.security[security_requirement_name];
29-
}
30-
3124
describe("getKiotaTree", () => {
3225
test('testGetKiotaTree_from_valid_File', async () => {
3326
const descriptionUrl = '../../tests/Kiota.Builder.IntegrationTests/DiscriminatorSample.yaml';
@@ -51,22 +44,33 @@ describe("getKiotaTree", () => {
5144
expect(existsGreaterThanLevelLogs(actual?.logs, LogLevel.warning)).toBeFalsy();
5245
expect(existsGreaterThanLevelLogs(actual?.logs, LogLevel.information)).toBeTruthy();
5346

47+
// Check if the security requirements are defined correctly for get operation
48+
// It should have one security requirement: oAuth2AuthCode
5449
const actualListOperationNode = findOperationByPath(actual, '\\repairs#GET');
5550
expect(actualListOperationNode).toBeDefined();
5651
expect(actualListOperationNode?.operationId).toEqual('listRepairs');
5752
expect(actualListOperationNode?.security).toBeDefined();
58-
const actualListSecurityRequirement = findSecurityRequirementByName(actualListOperationNode, 'oAuth2AuthCode');
59-
expect(actualListSecurityRequirement).toBeDefined();
60-
expect(actualListSecurityRequirement?.[0]).toEqual('api://sample/repairs_read');
61-
53+
expect(actualListOperationNode?.security?.length).toEqual(1);
54+
const firstSecurityRequirementInGet = actualListOperationNode?.security?.[0];
55+
const oAuth2AuthCodeSecurityRequirementInGet = firstSecurityRequirementInGet?.['oAuth2AuthCode'];
56+
expect(oAuth2AuthCodeSecurityRequirementInGet).toBeDefined();
57+
expect(oAuth2AuthCodeSecurityRequirementInGet?.length).toEqual(1);
58+
expect(oAuth2AuthCodeSecurityRequirementInGet?.[0]).toEqual('api://sample/repairs_read');
59+
60+
// Check if the security requirements are defined correctly for post operation
61+
// It should have two security requirements: oAuth2AuthCode and httpAuth
6262
const actualPostOperationNode = findOperationByPath(actual, '\\repairs#POST');
6363
expect(actualPostOperationNode).toBeDefined();
6464
expect(actualPostOperationNode?.operationId).toEqual('repairs_post');
6565
expect(actualPostOperationNode?.security).toBeDefined();
66-
const actualPostSecurityRequirement = findSecurityRequirementByName(actualPostOperationNode, 'oAuth2AuthCode');
67-
expect(actualPostSecurityRequirement).toBeDefined();
68-
expect(actualPostSecurityRequirement?.[0]).toEqual('api://sample/repairs_write');
69-
66+
expect(actualPostOperationNode?.security?.length).toEqual(1);
67+
const firstSecurityRequirementInPost = actualPostOperationNode?.security?.[0];
68+
const oAuth2AuthCodeSecurityRequirementInPost = firstSecurityRequirementInPost?.['oAuth2AuthCode'];
69+
expect(oAuth2AuthCodeSecurityRequirementInPost).toBeDefined();
70+
expect(oAuth2AuthCodeSecurityRequirementInPost?.length).toEqual(1);
71+
expect(oAuth2AuthCodeSecurityRequirementInPost?.[0]).toEqual('api://sample/repairs_write');
72+
73+
// Check if the security schemes are defined correctly for post operation
7074
const actualOAuthSecuritySchema = actual?.securitySchemes?.["oAuth2AuthCode"] as OAuth2SecurityScheme;
7175
expect(actualOAuthSecuritySchema).toBeDefined();
7276
expect(actualOAuthSecuritySchema.flows).toBeDefined();
@@ -121,6 +125,116 @@ describe("getKiotaTree", () => {
121125

122126
});
123127

128+
test('testGetKiotaTree_withMultipleSecurity', async () => {
129+
const descriptionUrl = '../../tests/Kiota.Builder.IntegrationTests/ModelWithMultipleSecurity.yaml';
130+
131+
const actual = await getKiotaTree({ includeFilters: [], descriptionPath: descriptionUrl, excludeFilters: [], clearCache: false });
132+
133+
expect(actual).toBeDefined();
134+
expect(existsGreaterThanLevelLogs(actual?.logs, LogLevel.warning)).toBeFalsy();
135+
expect(existsGreaterThanLevelLogs(actual?.logs, LogLevel.information)).toBeTruthy();
136+
137+
// Check if the security requirements are defined correctly for get operation
138+
// It should have one security requirement: oAuth2AuthCode
139+
const actualListOperationNode = findOperationByPath(actual, '\\repairs#GET');
140+
expect(actualListOperationNode).toBeDefined();
141+
expect(actualListOperationNode?.operationId).toEqual('listRepairs');
142+
expect(actualListOperationNode?.security).toBeDefined();
143+
expect(actualListOperationNode?.security?.length).toEqual(1);
144+
const firstSecurityRequirementInGet = actualListOperationNode?.security?.[0];
145+
const oAuth2AuthCodeSecurityRequirementInGet = firstSecurityRequirementInGet?.['oAuth2AuthCode'];
146+
expect(oAuth2AuthCodeSecurityRequirementInGet).toBeDefined();
147+
expect(oAuth2AuthCodeSecurityRequirementInGet?.length).toEqual(1);
148+
expect(oAuth2AuthCodeSecurityRequirementInGet?.[0]).toEqual('api://sample/repairs_read');
149+
150+
// Check if the security requirements are defined correctly for post operation
151+
// It should have two security requirements: oAuth2AuthCode and httpAuth
152+
const actualPostOperationNode = findOperationByPath(actual, '\\repairs#POST');
153+
expect(actualPostOperationNode).toBeDefined();
154+
expect(actualPostOperationNode?.operationId).toEqual('repairs_post');
155+
expect(actualPostOperationNode?.security).toBeDefined();
156+
expect(actualPostOperationNode?.security?.length).toEqual(2);
157+
const firstSecurityRequirementInPost = actualPostOperationNode?.security?.[0];
158+
const oAuth2AuthCodeSecurityRequirementInPost = firstSecurityRequirementInPost?.['oAuth2AuthCode'];
159+
expect(oAuth2AuthCodeSecurityRequirementInPost).toBeDefined();
160+
expect(oAuth2AuthCodeSecurityRequirementInPost?.length).toEqual(1);
161+
expect(oAuth2AuthCodeSecurityRequirementInPost?.[0]).toEqual('api://sample/repairs_write');
162+
const secondSecurityRequirementInPost = actualPostOperationNode?.security?.[1];
163+
expect(secondSecurityRequirementInPost).toBeDefined();
164+
const httpAuthSecurityRequirementInPost = secondSecurityRequirementInPost?.['httpAuth'];
165+
expect(httpAuthSecurityRequirementInPost).toBeDefined();
166+
expect(httpAuthSecurityRequirementInPost?.length).toEqual(0);
167+
168+
// Check if the security requirements are defined correctly for post operation
169+
// It should have two security requirements: oAuth2AuthCode and httpAuth
170+
const actualPutOperationNode = findOperationByPath(actual, '\\repairs#PUT');
171+
expect(actualPutOperationNode).toBeDefined();
172+
expect(actualPutOperationNode?.operationId).toEqual('repairs_put');
173+
expect(actualPutOperationNode?.security).toBeDefined();
174+
expect(actualPutOperationNode?.security?.length).toEqual(1);
175+
const firstSecurityRequirementInPut = actualPutOperationNode?.security?.[0];
176+
const oAuth2AuthCodeSecurityRequirementInPut = firstSecurityRequirementInPut?.['oAuth2AuthCode'];
177+
expect(oAuth2AuthCodeSecurityRequirementInPut).toBeDefined();
178+
expect(oAuth2AuthCodeSecurityRequirementInPut?.length).toEqual(1);
179+
expect(oAuth2AuthCodeSecurityRequirementInPut?.[0]).toEqual('api://sample/repairs_write');
180+
const httpAuthSecurityRequirementInPut = firstSecurityRequirementInPut?.['httpAuth'];
181+
expect(httpAuthSecurityRequirementInPut).toBeDefined();
182+
expect(httpAuthSecurityRequirementInPut?.length).toEqual(0);
183+
184+
// Check if the security schemes are defined correctly for post operation
185+
const actualOAuthSecuritySchema = actual?.securitySchemes?.["oAuth2AuthCode"] as OAuth2SecurityScheme;
186+
expect(actualOAuthSecuritySchema).toBeDefined();
187+
expect(actualOAuthSecuritySchema.flows).toBeDefined();
188+
const actualAuthorizationCodeFlow = actualOAuthSecuritySchema.flows.authorizationCode;
189+
expect(actualAuthorizationCodeFlow).toBeDefined();
190+
expect(actualAuthorizationCodeFlow?.authorizationUrl).toEqual('https://login.microsoftonline.com/common/oauth2/v2.0/authorize');
191+
expect(actualAuthorizationCodeFlow?.tokenUrl).toEqual('https://login.microsoftonline.com/common/oauth2/v2.0/token');
192+
expect(actualAuthorizationCodeFlow?.refreshUrl).toEqual('https://login.microsoftonline.com/common/oauth2/v2.0/refresh');
193+
expect(actualAuthorizationCodeFlow?.scopes).toBeDefined();
194+
expect(actualAuthorizationCodeFlow?.scopes['api://sample/repairs_read']).toEqual('Read repair records');
195+
expect(actualAuthorizationCodeFlow?.scopes['api://sample/repairs_write']).toEqual('Write repair records');
196+
const actualImplicitFlow = actualOAuthSecuritySchema.flows.implicit;
197+
expect(actualImplicitFlow).toBeDefined();
198+
expect(actualImplicitFlow?.authorizationUrl).toEqual('https://login.microsoftonline.com/common/oauth2/v2.0/authorize');
199+
expect(actualImplicitFlow?.refreshUrl).toEqual('https://login.microsoftonline.com/common/oauth2/v2.0/refresh');
200+
expect(actualImplicitFlow?.scopes).toBeDefined();
201+
expect(actualImplicitFlow?.scopes['api://sample/repairs_read']).toEqual('Read repair records');
202+
expect(actualImplicitFlow?.scopes['api://sample/repairs_write']).toEqual('Write repair records');
203+
const actualClientCredentialsFlow = actualOAuthSecuritySchema.flows.clientCredentials;
204+
expect(actualClientCredentialsFlow).toBeDefined();
205+
expect(actualClientCredentialsFlow?.tokenUrl).toEqual('https://login.microsoftonline.com/common/oauth2/v2.0/token');
206+
expect(actualClientCredentialsFlow?.refreshUrl).toEqual('https://login.microsoftonline.com/common/oauth2/v2.0/refresh');
207+
expect(actualClientCredentialsFlow?.scopes).toBeDefined();
208+
expect(actualClientCredentialsFlow?.scopes['api://sample/repairs_read']).toEqual('Read repair records');
209+
expect(actualClientCredentialsFlow?.scopes['api://sample/repairs_write']).toEqual('Write repair records');
210+
const actualPasswordFlow = actualOAuthSecuritySchema.flows.password;
211+
expect(actualPasswordFlow).toBeDefined();
212+
expect(actualPasswordFlow?.tokenUrl).toEqual('https://login.microsoftonline.com/common/oauth2/v2.0/token');
213+
expect(actualPasswordFlow?.refreshUrl).toEqual('https://login.microsoftonline.com/common/oauth2/v2.0/refresh');
214+
expect(actualPasswordFlow?.scopes).toBeDefined();
215+
expect(actualPasswordFlow?.scopes['api://sample/repairs_read']).toEqual('Read repair records');
216+
expect(actualPasswordFlow?.scopes['api://sample/repairs_write']).toEqual('Write repair records');
217+
218+
const actualHttpSecuritySchema = actual?.securitySchemes?.["httpAuth"] as HttpSecurityScheme;
219+
expect(actualHttpSecuritySchema).toBeDefined();
220+
expect(actualHttpSecuritySchema.type).toEqual('http');
221+
expect(actualHttpSecuritySchema.scheme).toEqual('basic');
222+
expect(actualHttpSecuritySchema.description).toEqual('HTTP basic authentication');
223+
224+
const actualApiKeySecuritySchema = actual?.securitySchemes?.["apiKeyAuth"] as ApiKeySecurityScheme;
225+
expect(actualApiKeySecuritySchema).toBeDefined();
226+
expect(actualApiKeySecuritySchema.type).toEqual('apiKey');
227+
expect(actualApiKeySecuritySchema.name).toEqual('X-API-Key');
228+
expect(actualApiKeySecuritySchema.in).toEqual('header');
229+
expect(actualApiKeySecuritySchema.description).toEqual('API key authentication');
230+
231+
const actualOpenIdConnectSecuritySchema = actual?.securitySchemes?.["openIdConnectAuth"] as OpenIdSecurityScheme;
232+
expect(actualOpenIdConnectSecuritySchema).toBeDefined();
233+
expect(actualOpenIdConnectSecuritySchema.type).toEqual('openIdConnect');
234+
expect(actualOpenIdConnectSecuritySchema.description).toEqual('OpenID Connect authentication');
235+
expect(actualOpenIdConnectSecuritySchema.openIdConnectUrl).toEqual('https://login.microsoftonline.com/common/.well-known/openid-configuration');
236+
});
237+
124238
test('testGetKiotaTree_withReferenceIdExtension', async () => {
125239
const descriptionUrl = '../../tests/Kiota.Builder.IntegrationTests/ModelWithRefIdExtension.yaml';
126240
const actual = await getKiotaTree({ includeFilters: [], descriptionPath: descriptionUrl, excludeFilters: [], clearCache: false });

0 commit comments

Comments
 (0)