example/source/RequestOAuth2FromSAPUsingAAD.cs (269 lines of code) (raw):
using System.Text;
using Azure.ApiManagement.PolicyToolkit.Authoring;
using Azure.ApiManagement.PolicyToolkit.Authoring.Expressions;
using Newtonsoft.Json.Linq;
namespace Contoso.Apis;
[Document]
public class RequestOAuth2FromSAPUsingAAD : IDocument
{
public void Inbound(IInboundContext context)
{
context.Base();
context.ValidateJwt(new ValidateJwtConfig
{
HeaderName = "Authorization",
FailedValidationHttpCode = 401,
RequireScheme = "Bearer",
OpenIdConfigs =
[
new OpenIdConfig
{
Url = "https://login.microsoftonline.com/{{AADTenantId}}/.well-known/openid-configuration"
}
],
Audiences = ["api://{{APIMAADRegisteredAppClientId}}"],
Issuers = ["https://login.microsoftonline.com/{{AADTenantId}}/v2.0"],
RequiredClaims =
[
new ClaimConfig { Name = "scp", Match = "all", Separator = " ", Values = ["user_impersonation"] }
]
});
context.SetHeader("Accept-Encoding", "gzip, deflate");
context.SetVariable("APIMAADRegisteredAppClientId", "{{APIMAADRegisteredAppClientId}}");
context.SetVariable("APIMAADRegisteredAppClientSecret", "{{APIMAADRegisteredAppClientSecret}}");
context.SetVariable("AADSAPResource", "{{AADSAPResource}}");
context.SetVariable("SAPOAuthClientID", "{{SAPOAuthClientID}}");
context.SetVariable("SAPOAuthClientSecret", "{{SAPOAuthClientSecret}}");
context.SetVariable("SAPOAuthScope", "{{SAPOAuthScope}}");
context.SetVariable("SAPOAuthRefreshExpiry", "{{SAPOAuthRefreshExpiry}}");
context.InlinePolicy("<cache-lookup-value key=\"@(\"SAPPrincipal\" + context.Request.Headers.GetValueOrDefault(\"Authorization\",\"\").AsJwt()?.Subject)\" variable-name=\"SAPBearerToken\" />");
context.InlinePolicy("<cache-lookup-value key=\"@(\"SAPPrincipalRefresh\" + context.Request.Headers.GetValueOrDefault(\"Authorization\",\"\").AsJwt()?.Subject)\" variable-name=\"SAPRefreshToken\" />");
if (ContainsSapTokens(context.ExpressionContext))
{
context.SendRequest(new SendRequestConfig
{
Mode = "new",
ResponseVariableName = "fetchSAMLAssertion",
Timeout = 10,
IgnoreError = false,
Url = "https://login.microsoftonline.com/{{AADTenantId}}/oauth2/v2.0/token",
Method = "POST",
Headers =
[
new HeaderConfig
{
Name = "Content-Type",
ExistsAction = "override",
Values = ["application/x-www-form-urlencoded"]
}
],
Body = new BodyConfig { Content = CreateAadTokenRequestBody(context.ExpressionContext) }
});
context.SetVariable("accessToken", GetTokenFromAadResponse(context.ExpressionContext));
context.SendRequest(new SendRequestConfig
{
Mode = "new",
ResponseVariableName = "ferchSapBearer",
Timeout = 10,
IgnoreError = false,
Url = "https://{{SAPOAuthServerAdressForTokenEndpoint}}/sap/bc/sec/oauth2/token",
Method = "POST",
Headers =
[
new HeaderConfig
{
Name = "Content-Type",
ExistsAction = "override",
Values = ["application/x-www-form-urlencoded"]
},
new HeaderConfig
{
Name = "Authorization",
ExistsAction = "override",
Values = [CreateAuthorizationHeaderToSAP(context.ExpressionContext)],
},
new HeaderConfig { Name = "Ocp-Apim-Subscription-Key", ExistsAction = "Delete" }
],
Body = new BodyConfig { Content = CreateSapTokenRequestBody(context.ExpressionContext) }
});
context.SetVariable("SAPResponseObject", GetSAPBearerResponseObject(context.ExpressionContext));
context.SetVariable("SAPBearerTokenExpiry", GetSAPBearerTokenExpiry(context.ExpressionContext));
context.SetVariable("iSAPBearerTokenExpiry", GetIntSAPBearerTokenExpiry(context.ExpressionContext));
context.SetVariable("SAPBearerToken", GetSAPBearerToken(context.ExpressionContext));
context.SetVariable("SAPRefreshToken", GetSAPRefreshToken(context.ExpressionContext));
context.SetVariable("RandomBackOffDelay", GetRandomBackOffDelay(context.ExpressionContext));
context.InlinePolicy(
"<cache-store-value key=\"@(\"SAPPrincipal\" + context.Request.Headers.GetValueOrDefault(\"Authorization\",\"\").AsJwt()?.Subject)\" value=\"@((string)context.Variables[\"SAPBearerToken\"])\" duration=\"@((int)context.Variables[\"iSAPBearerTokenExpiry\"] - (int)context.Variables[\"RandomBackOffDelay\"])\" />");
context.InlinePolicy(
"<cache-store-value key=\"@(\"SAPPrincipalRefresh\" + context.Request.Headers.GetValueOrDefault(\"Authorization\",\"\").AsJwt()?.Subject)\" value=\"@((string)context.Variables[\"SAPRefreshToken\"])\" duration=\"@(int.Parse((string)context.Variables[\"SAPOAuthRefreshExpiry\"]) - (int)context.Variables[\"RandomBackOffDelay\"])\" />");
}
else if (ContainsRefreshTokenOnly(context.ExpressionContext))
{
context.SendRequest(new SendRequestConfig
{
Mode = "new",
ResponseVariableName = "fetchrefreshedSAPBearer",
Timeout = 10,
IgnoreError = false,
Url = "https://{{SAPOAuthServerAdressForTokenEndpoint}}/sap/bc/sec/oauth2/token",
Method = "POST",
Headers =
[
new HeaderConfig
{
Name = "Content-Type",
ExistsAction = "override",
Values = ["application/x-www-form-urlencoded"]
},
new HeaderConfig
{
Name = "Authorization",
ExistsAction = "override",
Values = [CreateAuthorizationHeaderToSAP(context.ExpressionContext)],
}
],
Body = new BodyConfig { Content = CreateSapRefreshTokenRequestBody(context.ExpressionContext) }
});
context.SetVariable("SAPRefreshedResponseObject", GetSAPRefreshResponseObject(context.ExpressionContext));
context.SetVariable("SAPBearerTokenExpiry", GetSAPBearerTokenExpiry(context.ExpressionContext));
context.SetVariable("iSAPBearerTokenExpiry", GetIntSAPBearerTokenExpiry(context.ExpressionContext));
context.SetVariable("SAPBearerToken", GetSAPBearerToken(context.ExpressionContext));
context.SetVariable("SAPRefreshToken", GetSAPRefreshToken(context.ExpressionContext));
context.SetVariable("RandomBackOffDelay", GetRandomBackOffDelay(context.ExpressionContext));
context.InlinePolicy("<cache-store-value key=\"@(\"SAPPrincipal\" + context.Request.Headers.GetValueOrDefault(\"Authorization\",\"\").AsJwt()?.Subject)\" value=\"@((string)context.Variables[\"SAPBearerToken\"])\" duration=\"@((int)context.Variables[\"iSAPBearerTokenExpiry\"] - (int)context.Variables[\"RandomBackOffDelay\"])\" />");
context.InlinePolicy("<cache-store-value key=\"@(\"SAPPrincipalRefresh\" + context.Request.Headers.GetValueOrDefault(\"Authorization\",\"\").AsJwt()?.Subject)\" value=\"@((string)context.Variables[\"SAPRefreshToken\"])\" duration=\"@(int.Parse((string)context.Variables[\"SAPOAuthRefreshExpiry\"]) - (int)context.Variables[\"RandomBackOffDelay\"])\" />");
}
if (IsNotGetOrHeadRequest(context.ExpressionContext))
{
context.SendRequest(new SendRequestConfig
{
Mode = "new",
ResponseVariableName = "SAPCSRFToken",
Timeout = 10,
IgnoreError = false,
Url = GetRequestURL(context.ExpressionContext),
Method = "HEAD",
Headers =
[
new HeaderConfig { Name = "X-CSRF-Token", ExistsAction = "override", Values = ["Fetch"] },
new HeaderConfig
{
Name = "Authorization",
ExistsAction = "override",
Values = [GetSAPAuthorizationBearerToken(context.ExpressionContext)],
}
],
});
if (IsCSRFRequestSuccessfull(context.ExpressionContext))
{
context.SetVariable("SAPCSRFToken", GetCSRFToken(context.ExpressionContext));
context.SetVariable("SAPXSRFCookie", GetXsrfCookie(context.ExpressionContext));
}
}
context.SetHeader("Authorization", GetSAPAuthorizationBearerToken(context.ExpressionContext));
context.RemoveHeader("Ocp-Apim-Subscription-Key");
if (IsGetNotToMetadataRequest(context.ExpressionContext))
{
context.SetHeader("$format", "json");
}
}
public void Backend(IBackendContext context)
{
context.Base();
}
public void Outbound(IOutboundContext context)
{
context.Base();
context.InlinePolicy("<find-and-replace from=\"@(context.Api.ServiceUrl.Host +\":\"+ context.Api.ServiceUrl.Port)\" to=\"@(context.Request.OriginalUrl.Host + \":\" + context.Request.OriginalUrl.Port + context.Api.Path)\" />");
}
public void OnError(IOnErrorContext context)
{
context.Base();
context.SetHeader("ErrorSource", GetErrorSource(context.ExpressionContext));
context.SetHeader("ErrorReason", GetErrorReason(context.ExpressionContext));
context.SetHeader("ErrorMessage", GetErrorMessage(context.ExpressionContext));
context.SetHeader("ErrorScope", GetErrorScope(context.ExpressionContext));
context.SetHeader("ErrorSection", GetErrorSection(context.ExpressionContext));
context.SetHeader("ErrorPath", GetErrorPath(context.ExpressionContext));
context.SetHeader("ErrorPolicyId", GetErrorPolicyId(context.ExpressionContext));
context.SetHeader("ErrorStatusCode", ErrorErrorStatusCode(context.ExpressionContext));
}
bool ContainsSapTokens(IExpressionContext context)
=> !context.Variables.ContainsKey("SAPBearerToken") && !context.Variables.ContainsKey("SAPRefreshToken");
string CreateAadTokenRequestBody(IExpressionContext context)
{
var _AADRegisteredAppClientId = context.Variables["APIMAADRegisteredAppClientId"];
var _AADRegisteredAppClientSecret = context.Variables["APIMAADRegisteredAppClientSecret"];
var _AADSAPResource = context.Variables["AADSAPResource"];
var assertion = context.Request.Headers.GetValueOrDefault("Authorization", "").Replace("Bearer ", "");
return $"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={assertion}&client_id={_AADRegisteredAppClientId}&client_secret={_AADRegisteredAppClientSecret}&scope={_AADSAPResource}/.default&requested_token_use=on_behalf_of&requested_token_type=urn:ietf:params:oauth:token-type:saml2";
}
string GetTokenFromAadResponse(IExpressionContext context)
=> (string)((IResponse)context.Variables["fetchSAMLAssertion"]).Body.As<JObject>()["access_token"];
string CreateAuthorizationHeaderToSAP(IExpressionContext context)
{
var _SAPOAuthClientID = context.Variables["SAPOAuthClientID"];
var _SAPOAuthClientSecret = context.Variables["SAPOAuthClientSecret"];
return "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_SAPOAuthClientID}:{_SAPOAuthClientSecret}"));
}
string CreateSapTokenRequestBody(IExpressionContext context)
{
var _SAPOAuthClientID = context.Variables["SAPOAuthClientID"];
var _SAPOAuthScope = context.Variables["SAPOAuthScope"];
var assertion2 = context.Variables["accessToken"];
return $"grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer&assertion={assertion2}&client_id={_SAPOAuthClientID}&scope={_SAPOAuthScope}";
}
string CreateSapRefreshTokenRequestBody(IExpressionContext context)
{
var _SAPOAuthClientID = context.Variables["SAPOAuthClientID"];
var _SAPOAuthScope = context.Variables["SAPOAuthScope"];
var _refreshToken = context.Variables["SAPRefreshToken"];
return $"grant_type=refresh_token&refresh_token={_refreshToken}&client_id={_SAPOAuthClientID}&scope={_SAPOAuthScope}";
}
JObject GetSAPBearerResponseObject(IExpressionContext context)
=> ((IResponse)context.Variables["fetchSAPBearer"]).Body.As<JObject>();
JObject GetSAPRefreshResponseObject(IExpressionContext context)
=> ((IResponse)context.Variables["fetchrefreshedSAPBearer"]).Body.As<JObject>();
string GetSAPBearerTokenExpiry(IExpressionContext context)
=> ((JObject)context.Variables["SAPResponseObject"])["expires_in"].ToString();
int GetIntSAPBearerTokenExpiry(IExpressionContext context)
=> int.Parse((string)context.Variables["SAPBearerTokenExpiry"]);
string GetSAPBearerToken(IExpressionContext context)
=> ((JObject)context.Variables["SAPResponseObject"])["access_token"].ToString();
string GetSAPRefreshToken(IExpressionContext context)
=> ((JObject)context.Variables["SAPResponseObject"])["refresh_token"].ToString();
double GetRandomBackOffDelay(IExpressionContext context)
=> new Random().Next(0, (int)context.Variables["iSAPBearerTokenExpiry"] / 3);
bool ContainsRefreshTokenOnly(IExpressionContext context)
=> !context.Variables.ContainsKey("SAPBearerToken") && context.Variables.ContainsKey("SAPRefreshToken");
bool IsNotGetOrHeadRequest(IExpressionContext context)
=> context.Request.Method != "GET" && context.Request.Method != "HEAD";
string GetRequestURL(IExpressionContext context) => context.Request.Url.ToString();
string GetSAPAuthorizationBearerToken(IExpressionContext context)
=> "Bearer " + (string)context.Variables["SAPBearerToken"];
bool IsCSRFRequestSuccessfull(IExpressionContext context)
=> ((IResponse)context.Variables["SAPCSRFToken"]).StatusCode == 200;
string GetCSRFToken(IExpressionContext context)
=> ((IResponse)context.Variables["SAPCSRFToken"]).Headers.GetValueOrDefault("x-csrf-token");
string GetXsrfCookie(IExpressionContext context)
{
string rawcookie = ((IResponse)context.Variables["SAPCSRFToken"]).Headers.GetValueOrDefault("Set-Cookie");
string[] cookies = rawcookie.Split(';');
string xsrftoken = cookies.FirstOrDefault(ss => ss.Contains("sap-XSRF"));
if (xsrftoken == null)
{
xsrftoken = cookies.FirstOrDefault(ss => ss.Contains("SAP_SESSIONID"));
}
return xsrftoken.Split(',')[1];
}
bool IsGetNotToMetadataRequest(IExpressionContext context)
=> !context.Request.Url.Path.Contains("/$metadata") && context.Request.Method == "GET";
string GetErrorSource(IExpressionContext context) => context.LastError.Source;
string GetErrorReason(IExpressionContext context) => context.LastError.Reason;
string GetErrorMessage(IExpressionContext context) => context.LastError.Message;
string GetErrorScope(IExpressionContext context) => context.LastError.Scope;
string GetErrorSection(IExpressionContext context) => context.LastError.Section;
string GetErrorPath(IExpressionContext context) => context.LastError.Path;
string GetErrorPolicyId(IExpressionContext context) => context.LastError.PolicyId;
string ErrorErrorStatusCode(IExpressionContext context) => context.Response.StatusCode.ToString();
}