UNPKG

7.36 kBPlain TextView Raw
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License. See License.txt in the project root for license information.
3import { HttpOperationResponse } from "../httpOperationResponse";
4import * as utils from "../util/utils";
5import { WebResourceLike } from "../webResource";
6import {
7 BaseRequestPolicy,
8 RequestPolicy,
9 RequestPolicyFactory,
10 RequestPolicyOptionsLike,
11} from "./requestPolicy";
12
13export function rpRegistrationPolicy(retryTimeout = 30): RequestPolicyFactory {
14 return {
15 create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => {
16 return new RPRegistrationPolicy(nextPolicy, options, retryTimeout);
17 },
18 };
19}
20
21export class RPRegistrationPolicy extends BaseRequestPolicy {
22 constructor(
23 nextPolicy: RequestPolicy,
24 options: RequestPolicyOptionsLike,
25 readonly _retryTimeout = 30
26 ) {
27 super(nextPolicy, options);
28 }
29
30 public sendRequest(request: WebResourceLike): Promise<HttpOperationResponse> {
31 return this._nextPolicy
32 .sendRequest(request.clone())
33 .then((response) => registerIfNeeded(this, request, response));
34 }
35}
36
37function registerIfNeeded(
38 policy: RPRegistrationPolicy,
39 request: WebResourceLike,
40 response: HttpOperationResponse
41): Promise<HttpOperationResponse> {
42 if (response.status === 409) {
43 const rpName = checkRPNotRegisteredError(response.bodyAsText as string);
44 if (rpName) {
45 const urlPrefix = extractSubscriptionUrl(request.url);
46 return (
47 registerRP(policy, urlPrefix, rpName, request)
48 // Autoregistration of ${provider} failed for some reason. We will not return this error
49 // instead will return the initial response with 409 status code back to the user.
50 // do nothing here as we are returning the original response at the end of this method.
51 .catch(() => false)
52 .then((registrationStatus) => {
53 if (registrationStatus) {
54 // Retry the original request. We have to change the x-ms-client-request-id
55 // otherwise Azure endpoint will return the initial 409 (cached) response.
56 request.headers.set("x-ms-client-request-id", utils.generateUuid());
57 return policy._nextPolicy.sendRequest(request.clone());
58 }
59 return response;
60 })
61 );
62 }
63 }
64
65 return Promise.resolve(response);
66}
67
68/**
69 * Reuses the headers of the original request and url (if specified).
70 * @param {WebResourceLike} originalRequest The original request
71 * @param {boolean} reuseUrlToo Should the url from the original request be reused as well. Default false.
72 * @returns {object} A new request object with desired headers.
73 */
74function getRequestEssentials(
75 originalRequest: WebResourceLike,
76 reuseUrlToo = false
77): WebResourceLike {
78 const reqOptions: WebResourceLike = originalRequest.clone();
79 if (reuseUrlToo) {
80 reqOptions.url = originalRequest.url;
81 }
82
83 // We have to change the x-ms-client-request-id otherwise Azure endpoint
84 // will return the initial 409 (cached) response.
85 reqOptions.headers.set("x-ms-client-request-id", utils.generateUuid());
86
87 // Set content-type to application/json
88 reqOptions.headers.set("Content-Type", "application/json; charset=utf-8");
89
90 return reqOptions;
91}
92
93/**
94 * Validates the error code and message associated with 409 response status code. If it matches to that of
95 * RP not registered then it returns the name of the RP else returns undefined.
96 * @param {string} body The response body received after making the original request.
97 * @returns {string} The name of the RP if condition is satisfied else undefined.
98 */
99function checkRPNotRegisteredError(body: string): string {
100 let result, responseBody;
101 if (body) {
102 try {
103 responseBody = JSON.parse(body);
104 } catch (err) {
105 // do nothing;
106 }
107 if (
108 responseBody &&
109 responseBody.error &&
110 responseBody.error.message &&
111 responseBody.error.code &&
112 responseBody.error.code === "MissingSubscriptionRegistration"
113 ) {
114 const matchRes = responseBody.error.message.match(/.*'(.*)'/i);
115 if (matchRes) {
116 result = matchRes.pop();
117 }
118 }
119 }
120 return result;
121}
122
123/**
124 * Extracts the first part of the URL, just after subscription:
125 * https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/
126 * @param {string} url The original request url
127 * @returns {string} The url prefix as explained above.
128 */
129function extractSubscriptionUrl(url: string): string {
130 let result;
131 const matchRes = url.match(/.*\/subscriptions\/[a-f0-9-]+\//gi);
132 if (matchRes && matchRes[0]) {
133 result = matchRes[0];
134 } else {
135 throw new Error(`Unable to extract subscriptionId from the given url - ${url}.`);
136 }
137 return result;
138}
139
140/**
141 * Registers the given provider.
142 * @param {RPRegistrationPolicy} policy The RPRegistrationPolicy this function is being called against.
143 * @param {string} urlPrefix https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/
144 * @param {string} provider The provider name to be registered.
145 * @param {WebResourceLike} originalRequest The original request sent by the user that returned a 409 response
146 * with a message that the provider is not registered.
147 * @param {registrationCallback} callback The callback that handles the RP registration
148 */
149function registerRP(
150 policy: RPRegistrationPolicy,
151 urlPrefix: string,
152 provider: string,
153 originalRequest: WebResourceLike
154): Promise<boolean> {
155 const postUrl = `${urlPrefix}providers/${provider}/register?api-version=2016-02-01`;
156 const getUrl = `${urlPrefix}providers/${provider}?api-version=2016-02-01`;
157 const reqOptions = getRequestEssentials(originalRequest);
158 reqOptions.method = "POST";
159 reqOptions.url = postUrl;
160
161 return policy._nextPolicy.sendRequest(reqOptions).then((response) => {
162 if (response.status !== 200) {
163 throw new Error(`Autoregistration of ${provider} failed. Please try registering manually.`);
164 }
165 return getRegistrationStatus(policy, getUrl, originalRequest);
166 });
167}
168
169/**
170 * Polls the registration status of the provider that was registered. Polling happens at an interval of 30 seconds.
171 * Polling will happen till the registrationState property of the response body is "Registered".
172 * @param {RPRegistrationPolicy} policy The RPRegistrationPolicy this function is being called against.
173 * @param {string} url The request url for polling
174 * @param {WebResourceLike} originalRequest The original request sent by the user that returned a 409 response
175 * with a message that the provider is not registered.
176 * @returns {Promise<boolean>} True if RP Registration is successful.
177 */
178function getRegistrationStatus(
179 policy: RPRegistrationPolicy,
180 url: string,
181 originalRequest: WebResourceLike
182): Promise<boolean> {
183 const reqOptions: any = getRequestEssentials(originalRequest);
184 reqOptions.url = url;
185 reqOptions.method = "GET";
186
187 return policy._nextPolicy.sendRequest(reqOptions).then((res) => {
188 const obj = res.parsedBody as any;
189 if (res.parsedBody && obj.registrationState && obj.registrationState === "Registered") {
190 return true;
191 } else {
192 return utils
193 .delay(policy._retryTimeout * 1000)
194 .then(() => getRegistrationStatus(policy, url, originalRequest));
195 }
196 });
197}