microsoft/typespec

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
archive/docusaurus-website

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