microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
51ae128d6283aa5dde4f77da12ef1eebec6808e2

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/http/src/auth.ts

163lines · modecode

1import { Operation, Program } from "@typespec/compiler";
2import { deepClone, deepEquals } from "@typespec/compiler/utils";
3import { getAuthentication } from "./decorators.js";
4import {
5 Authentication,
6 AuthenticationOptionReference,
7 AuthenticationReference,
8 HttpAuth,
9 HttpAuthRef,
10 HttpService,
11 HttpServiceAuthentication,
12 OAuth2Flow,
13 OAuth2Scope,
14 Oauth2Auth,
15} from "./types.js";
16
17/**
18 * Resolve the authentication for a given operation.
19 * @param program Program
20 * @param operation Operation
21 * @returns Authentication provided on the operation or containing interface or namespace.
22 */
23export function getAuthenticationForOperation(
24 program: Program,
25 operation: Operation
26): Authentication | undefined {
27 const operationAuth = getAuthentication(program, operation);
28 if (operationAuth) {
29 return operationAuth;
30 }
31 if (operation.interface !== undefined) {
32 const interfaceAuth = getAuthentication(program, operation.interface);
33 if (interfaceAuth) {
34 return interfaceAuth;
35 }
36 }
37
38 let namespace = operation.namespace;
39
40 while (namespace) {
41 const namespaceAuth = getAuthentication(program, namespace);
42 if (namespaceAuth) {
43 return namespaceAuth;
44 }
45 namespace = namespace.namespace;
46 }
47 return undefined;
48}
49
50/**
51 * Compute the authentication for a given service.
52 * @param service Http Service
53 * @returns The normalized authentication for a service.
54 */
55export function resolveAuthentication(service: HttpService): HttpServiceAuthentication {
56 let schemes: Record<string, HttpAuth> = {};
57 let defaultAuth: AuthenticationReference = { options: [] };
58 const operationsAuth: Map<Operation, AuthenticationReference> = new Map();
59
60 if (service.authentication) {
61 const { newServiceSchemes, authOptions } = gatherAuth(service.authentication, {});
62 schemes = newServiceSchemes;
63 defaultAuth = authOptions;
64 }
65
66 for (const op of service.operations) {
67 if (op.authentication) {
68 const { newServiceSchemes, authOptions } = gatherAuth(op.authentication, schemes);
69 schemes = newServiceSchemes;
70 operationsAuth.set(op.operation, authOptions);
71 }
72 }
73
74 return { schemes: Object.values(schemes), defaultAuth, operationsAuth };
75}
76
77function gatherAuth(
78 authentication: Authentication,
79 serviceSchemes: Record<string, HttpAuth>
80): {
81 newServiceSchemes: Record<string, HttpAuth>;
82 authOptions: AuthenticationReference;
83} {
84 const newServiceSchemes: Record<string, HttpAuth> = serviceSchemes;
85 const authOptions: AuthenticationReference = { options: [] };
86 for (const option of authentication.options) {
87 const authOption: AuthenticationOptionReference = { all: [] };
88 for (const optionScheme of option.schemes) {
89 const serviceScheme = serviceSchemes[optionScheme.id];
90 let newServiceScheme = optionScheme;
91 if (serviceScheme) {
92 // If we've seen a different scheme by this id,
93 // Make sure to not overwrite it
94 if (!authsAreEqual(serviceScheme, optionScheme)) {
95 while (serviceSchemes[newServiceScheme.id]) {
96 newServiceScheme.id = newServiceScheme.id + "_";
97 }
98 }
99 // Merging scopes when encountering the same Oauth2 scheme
100 else if (serviceScheme.type === "oauth2" && optionScheme.type === "oauth2") {
101 const x = mergeOAuthScopes(serviceScheme, optionScheme);
102 newServiceScheme = x;
103 }
104 }
105 const httpAuthRef = makeHttpAuthRef(optionScheme, newServiceScheme);
106 newServiceSchemes[newServiceScheme.id] = newServiceScheme;
107 authOption.all.push(httpAuthRef);
108 }
109 authOptions.options.push(authOption);
110 }
111 return { newServiceSchemes, authOptions };
112}
113
114function makeHttpAuthRef(local: HttpAuth, reference: HttpAuth): HttpAuthRef {
115 if (reference.type === "oauth2" && local.type === "oauth2") {
116 const scopes: string[] = [];
117 for (const flow of local.flows) {
118 scopes.push(...flow.scopes.map((x) => x.value));
119 }
120 return { kind: "oauth2", auth: reference, scopes: scopes };
121 } else if (reference.type === "noAuth") {
122 return { kind: "noAuth", auth: reference };
123 } else {
124 return { kind: "any", auth: reference };
125 }
126}
127
128function mergeOAuthScopes<Flows extends OAuth2Flow[]>(
129 scheme1: Oauth2Auth<Flows>,
130 scheme2: Oauth2Auth<Flows>
131): Oauth2Auth<Flows> {
132 const flows = deepClone(scheme1.flows);
133 flows.forEach((flow1, i) => {
134 const flow2 = scheme2.flows[i];
135 const scopes = Array.from(new Set(flow1.scopes.concat(flow2.scopes)));
136 flows[i].scopes = scopes;
137 });
138 return {
139 ...scheme1,
140 flows,
141 };
142}
143
144function setOauth2Scopes<Flows extends OAuth2Flow[]>(
145 scheme: Oauth2Auth<Flows>,
146 scopes: OAuth2Scope[]
147): Oauth2Auth<Flows> {
148 const flows: Flows = deepClone(scheme.flows);
149 flows.forEach((flow) => {
150 flow.scopes = scopes;
151 });
152 return {
153 ...scheme,
154 flows,
155 };
156}
157
158function authsAreEqual(scheme1: HttpAuth, scheme2: HttpAuth): boolean {
159 if (scheme1.type === "oauth2" && scheme2.type === "oauth2") {
160 return deepEquals(setOauth2Scopes(scheme1, []), setOauth2Scopes(scheme2, []));
161 }
162 return deepEquals(scheme1, scheme2);
163}
164