From a712c9078d30bd9b8bea82941e4484d785e95bcf Mon Sep 17 00:00:00 2001 From: Ajith Thanam Date: Tue, 17 Oct 2023 17:19:12 -0400 Subject: [PATCH 1/4] impl for pma endpoint override --- .../CallAutomationClientBuilder.java | 29 ++- ...CustomBearerTokenAuthenticationPolicy.java | 204 ++++++++++++++++++ 2 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java index 40f38dbb2670..6b44c6fb7753 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java @@ -70,6 +70,7 @@ public final class CallAutomationClientBuilder implements private final ClientLogger logger = new ClientLogger(CallAutomationClientBuilder.class); private String connectionString; private String endpoint; + private String pmaEnpoint; private String hostName; private AzureKeyCredential azureKeyCredential; private TokenCredential tokenCredential; @@ -103,6 +104,11 @@ public CallAutomationClientBuilder endpoint(String endpoint) { return this; } + public CallAutomationClientBuilder pmaEndpoint(String endpoint) { + this.pmaEnpoint= Objects.requireNonNull(endpoint, "'pmaEndpoint' cannot be null."); + return this; + } + /** * Sets the {@link HttpPipeline} to use for the service client. * @@ -387,8 +393,13 @@ private AzureCommunicationCallAutomationServiceImpl createServiceImpl() { } AzureCommunicationCallAutomationServiceImplBuilder clientBuilder = new AzureCommunicationCallAutomationServiceImplBuilder(); - clientBuilder.endpoint(endpoint).pipeline(builderPipeline); - + + if(pmaEnpoint != null) { + clientBuilder.endpoint(pmaEnpoint).pipeline(builderPipeline); + } else { + clientBuilder.endpoint(endpoint).pipeline(builderPipeline); + } + return clientBuilder.buildClient(); } @@ -413,11 +424,15 @@ private List createHttpPipelineAuthPolicies(boolean isCustom List pipelinePolicies = new ArrayList<>(); if (tokenCredential != null) { - pipelinePolicies.add(new BearerTokenAuthenticationPolicy(tokenCredential, - "https://communication.azure.com//.default")); - Map httpHeaders = new HashMap<>(); - httpHeaders.put("x-ms-host", hostName); - pipelinePolicies.add(new AddHeadersPolicy(new HttpHeaders(httpHeaders))); + if(pmaEnpoint != null) { + pipelinePolicies.add(new CustomBearerTokenAuthenticationPolicy(tokenCredential, endpoint, "https://communication.azure.com//.default")); + } else { + pipelinePolicies.add(new BearerTokenAuthenticationPolicy(tokenCredential, + "https://communication.azure.com//.default")); + Map httpHeaders = new HashMap<>(); + httpHeaders.put("x-ms-host", hostName); + pipelinePolicies.add(new AddHeadersPolicy(new HttpHeaders(httpHeaders))); + } } else if (azureKeyCredential != null) { if (isCustomEndpointUsed) { String acsEndpoint = (new CommunicationConnectionString(connectionString)).getEndpoint(); diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java new file mode 100644 index 000000000000..93229bfb7d9c --- /dev/null +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java @@ -0,0 +1,204 @@ +package com.azure.communication.callautomation; + + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRequestContext; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpPipelineCallContext; +import com.azure.core.http.HttpPipelineNextPolicy; +import com.azure.core.http.HttpPipelineNextSyncPolicy; +import com.azure.core.http.HttpResponse; +import com.azure.core.http.policy.HttpPipelinePolicy; + +//import com.azure.core.implementation.AccessTokenCache; +import com.azure.core.util.logging.ClientLogger; + +//import io.netty.handler.codec.http.HttpMessage; +import reactor.core.publisher.Mono; + +import java.util.Objects; + +/** + * The pipeline policy that applies a token credential to an HTTP request with "Bearer" scheme. + */ +public class CustomBearerTokenAuthenticationPolicy implements HttpPipelinePolicy { + private static final ClientLogger LOGGER = new ClientLogger(CustomBearerTokenAuthenticationPolicy.class); + private static final String BEARER = "Bearer"; + + private final String[] scopes; + private final String acsEndpoint; + private final TokenCredential tokenCredential; + + /** + * Creates BearerTokenAuthenticationPolicy. + * + * @param credential the token credential to authenticate the request + * @param scopes the scopes of authentication the credential should get token for + */ + public CustomBearerTokenAuthenticationPolicy(TokenCredential credential, String acsEndpoint, String... scopes) { + Objects.requireNonNull(credential); + this.scopes = scopes; + this.acsEndpoint = acsEndpoint; + this.tokenCredential = credential; + } + + /** + * Executed before sending the initial request and authenticates the request. + * + * @param context The request context. + * @return A {@link Mono} containing {@link Void} + */ + public Mono authorizeRequest(HttpPipelineCallContext context) { + if (this.scopes == null) { + return Mono.empty(); + } + return setAuthorizationHeaderHelper(context, new TokenRequestContext().addScopes(this.scopes), false); + } + + /** + * Synchronously executed before sending the initial request and authenticates the request. + * + * @param context The request context. + */ + public void authorizeRequestSync(HttpPipelineCallContext context) { + setAuthorizationHeaderHelperSync(context, new TokenRequestContext().addScopes(scopes), false); + } + + /** + * Handles the authentication challenge in the event a 401 response with a WWW-Authenticate authentication challenge + * header is received after the initial request and returns appropriate {@link TokenRequestContext} to be used for + * re-authentication. + * + * @param context The request context. + * @param response The Http Response containing the authentication challenge header. + * @return A {@link Mono} containing {@link TokenRequestContext} + */ + public Mono authorizeRequestOnChallenge(HttpPipelineCallContext context, HttpResponse response) { + return Mono.just(false); + } + + /** + * Handles the authentication challenge in the event a 401 response with a WWW-Authenticate authentication challenge + * header is received after the initial request and returns appropriate {@link TokenRequestContext} to be used for + * re-authentication. + * + * @param context The request context. + * @param response The Http Response containing the authentication challenge header. + * @return A boolean indicating if containing the {@link TokenRequestContext} for re-authentication + */ + public boolean authorizeRequestOnChallengeSync(HttpPipelineCallContext context, HttpResponse response) { + return false; + } + + @Override + public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) { + addHeaders(context.getHttpRequest().getHeaders()); + if ("http".equals(context.getHttpRequest().getUrl().getProtocol())) { + return Mono.error(new RuntimeException("token credentials require a URL using the HTTPS protocol scheme")); + } + HttpPipelineNextPolicy nextPolicy = next.clone(); + + return authorizeRequest(context) + .then(Mono.defer(next::process)) + .flatMap(httpResponse -> { + String authHeader = httpResponse.getHeaderValue(HttpHeaderName.WWW_AUTHENTICATE); + if (httpResponse.getStatusCode() == 401 && authHeader != null) { + return authorizeRequestOnChallenge(context, httpResponse).flatMap(retry -> { + if (retry) { + // Both Netty and OkHttp expect the requestBody to be closed after the response has been read. + // Failure to do so results in memory leak. + // In case of StreamResponse (or other scenarios where we do not eagerly read the response) + // the response body may not be consumed. + // This can cause potential leaks in the scenarios like above, where the policy + // may intercept the response and it may never be read. + // Forcing the read here - so that the memory can be released. + return httpResponse.getBody().ignoreElements() + .then(nextPolicy.process()); + } else { + return Mono.just(httpResponse); + } + }); + } + return Mono.just(httpResponse); + }); + } + + @Override + public HttpResponse processSync(HttpPipelineCallContext context, HttpPipelineNextSyncPolicy next) { + addHeaders(context.getHttpRequest().getHeaders()); + if ("http".equals(context.getHttpRequest().getUrl().getProtocol())) { + throw LOGGER.logExceptionAsError( + new RuntimeException("token credentials require a URL using the HTTPS protocol scheme")); + } + HttpPipelineNextSyncPolicy nextPolicy = next.clone(); + + authorizeRequestSync(context); + HttpResponse httpResponse = next.processSync(); + String authHeader = httpResponse.getHeaderValue(HttpHeaderName.WWW_AUTHENTICATE); + if (httpResponse.getStatusCode() == 401 && authHeader != null) { + if (authorizeRequestOnChallengeSync(context, httpResponse)) { + // Both Netty and OkHttp expect the requestBody to be closed after the response has been read. + // Failure to do so results in memory leak. + // In case of StreamResponse (or other scenarios where we do not eagerly read the response) + // the response body may not be consumed. + // This can cause potential leaks in the scenarios like above, where the policy + // may intercept the response and it may never be read. + // Forcing the read here - so that the memory can be released. + return nextPolicy.processSync(); + } else { + return httpResponse; + } + } + return httpResponse; + } + + /** + * Authorizes the request with the bearer token acquired using the specified {@code tokenRequestContext} + * + * @param context the HTTP pipeline context. + * @param tokenRequestContext the token request context to be used for token acquisition. + * @return a {@link Mono} containing {@link Void} + */ + public Mono setAuthorizationHeader(HttpPipelineCallContext context, TokenRequestContext tokenRequestContext) { + return setAuthorizationHeaderHelper(context, tokenRequestContext, true); + } + + /** + * Authorizes the request with the bearer token acquired using the specified {@code tokenRequestContext} + * + * @param context the HTTP pipeline context. + * @param tokenRequestContext the token request context to be used for token acquisition. + */ + public void setAuthorizationHeaderSync(HttpPipelineCallContext context, TokenRequestContext tokenRequestContext) { + setAuthorizationHeaderHelperSync(context, tokenRequestContext, true); + } + + private Mono setAuthorizationHeaderHelper(HttpPipelineCallContext context, + TokenRequestContext tokenRequestContext, boolean checkToForceFetchToken) { + return tokenCredential.getToken(tokenRequestContext).flatMap(token1 -> { + setAuthorizationHeader(context.getHttpRequest().getHeaders(), token1.getToken()); + return Mono.empty(); + }); + } + + private void setAuthorizationHeaderHelperSync(HttpPipelineCallContext context, + TokenRequestContext tokenRequestContext, boolean checkToForceFetchToken) { + AccessToken token = tokenCredential.getTokenSync(tokenRequestContext); + setAuthorizationHeader(context.getHttpRequest().getHeaders(), token.getToken()); + } + + private static void setAuthorizationHeader(HttpHeaders headers, String token) { + headers.set(HttpHeaderName.AUTHORIZATION, BEARER + " " + token); + } + + private void addHeaders(HttpHeaders headers) + { + if (acsEndpoint != null) { + headers.set(HttpHeaderName.fromString("X-FORWARDED-HOST"), acsEndpoint); + } + } +} From 941dd3f191cab12a9847afa8fce70859963e42c0 Mon Sep 17 00:00:00 2001 From: Ajith Thanam Date: Tue, 17 Oct 2023 17:38:27 -0400 Subject: [PATCH 2/4] checkstyle + javadoc fix --- .../CallAutomationClientBuilder.java | 14 ++++++++++---- .../CustomBearerTokenAuthenticationPolicy.java | 12 ++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java index 6b44c6fb7753..45fe2e9c417f 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java @@ -104,8 +104,14 @@ public CallAutomationClientBuilder endpoint(String endpoint) { return this; } - public CallAutomationClientBuilder pmaEndpoint(String endpoint) { - this.pmaEnpoint= Objects.requireNonNull(endpoint, "'pmaEndpoint' cannot be null."); + /** + * Set pma endpoint override of the service. + * + * @param pmaEndpoint url of the service. + * @return CallAutomationClientBuilder object. + */ + public CallAutomationClientBuilder pmaEndpoint(String pmaEndpoint) { + this.pmaEnpoint = Objects.requireNonNull(pmaEndpoint, "'pmaEndpoint' cannot be null."); return this; } @@ -394,7 +400,7 @@ private AzureCommunicationCallAutomationServiceImpl createServiceImpl() { AzureCommunicationCallAutomationServiceImplBuilder clientBuilder = new AzureCommunicationCallAutomationServiceImplBuilder(); - if(pmaEnpoint != null) { + if (pmaEnpoint != null) { clientBuilder.endpoint(pmaEnpoint).pipeline(builderPipeline); } else { clientBuilder.endpoint(endpoint).pipeline(builderPipeline); @@ -424,7 +430,7 @@ private List createHttpPipelineAuthPolicies(boolean isCustom List pipelinePolicies = new ArrayList<>(); if (tokenCredential != null) { - if(pmaEnpoint != null) { + if (pmaEnpoint != null) { pipelinePolicies.add(new CustomBearerTokenAuthenticationPolicy(tokenCredential, endpoint, "https://communication.azure.com//.default")); } else { pipelinePolicies.add(new BearerTokenAuthenticationPolicy(tokenCredential, diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java index 93229bfb7d9c..d2692eb322db 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java @@ -1,8 +1,8 @@ -package com.azure.communication.callautomation; - - // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + +package com.azure.communication.callautomation; + import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; import com.azure.core.credential.TokenRequestContext; @@ -13,13 +13,8 @@ import com.azure.core.http.HttpPipelineNextSyncPolicy; import com.azure.core.http.HttpResponse; import com.azure.core.http.policy.HttpPipelinePolicy; - -//import com.azure.core.implementation.AccessTokenCache; import com.azure.core.util.logging.ClientLogger; - -//import io.netty.handler.codec.http.HttpMessage; import reactor.core.publisher.Mono; - import java.util.Objects; /** @@ -37,6 +32,7 @@ public class CustomBearerTokenAuthenticationPolicy implements HttpPipelinePolicy * Creates BearerTokenAuthenticationPolicy. * * @param credential the token credential to authenticate the request + * @param acsEndpoint endpoint of the ACS resource * @param scopes the scopes of authentication the credential should get token for */ public CustomBearerTokenAuthenticationPolicy(TokenCredential credential, String acsEndpoint, String... scopes) { From 7cb758cb76957535955caf31d6eac6dcaa25cc8c Mon Sep 17 00:00:00 2001 From: Ajith Thanam Date: Tue, 17 Oct 2023 18:07:37 -0400 Subject: [PATCH 3/4] temp exclude policy in test --- sdk/communication/azure-communication-callautomation/pom.xml | 3 +++ .../callautomation/CustomBearerTokenAuthenticationPolicy.java | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/communication/azure-communication-callautomation/pom.xml b/sdk/communication/azure-communication-callautomation/pom.xml index 271f1d88f5a9..936347f02541 100644 --- a/sdk/communication/azure-communication-callautomation/pom.xml +++ b/sdk/communication/azure-communication-callautomation/pom.xml @@ -158,6 +158,9 @@ com/azure/communication/callautomation/*.class + + com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.class + diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java index d2692eb322db..5ba820fc5a97 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java @@ -191,8 +191,7 @@ private static void setAuthorizationHeader(HttpHeaders headers, String token) { headers.set(HttpHeaderName.AUTHORIZATION, BEARER + " " + token); } - private void addHeaders(HttpHeaders headers) - { + private void addHeaders(HttpHeaders headers) { if (acsEndpoint != null) { headers.set(HttpHeaderName.fromString("X-FORWARDED-HOST"), acsEndpoint); } From dc2bc1d9e64eca4c7e97a12b686cf34615554175 Mon Sep 17 00:00:00 2001 From: Ajith Thanam Date: Wed, 18 Oct 2023 12:18:04 -0400 Subject: [PATCH 4/4] code clean up --- .../pom.xml | 3 --- .../CallAutomationClientBuilder.java | 20 +++++++++---------- ...CustomBearerTokenAuthenticationPolicy.java | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) rename sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/{ => implementation}/CustomBearerTokenAuthenticationPolicy.java (99%) diff --git a/sdk/communication/azure-communication-callautomation/pom.xml b/sdk/communication/azure-communication-callautomation/pom.xml index 936347f02541..271f1d88f5a9 100644 --- a/sdk/communication/azure-communication-callautomation/pom.xml +++ b/sdk/communication/azure-communication-callautomation/pom.xml @@ -158,9 +158,6 @@ com/azure/communication/callautomation/*.class - - com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.class - diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java index 45fe2e9c417f..6e7641518609 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationClientBuilder.java @@ -5,6 +5,7 @@ import com.azure.communication.callautomation.implementation.AzureCommunicationCallAutomationServiceImpl; import com.azure.communication.callautomation.implementation.AzureCommunicationCallAutomationServiceImplBuilder; +import com.azure.communication.callautomation.implementation.CustomBearerTokenAuthenticationPolicy; import com.azure.communication.callautomation.implementation.CustomHmacAuthenticationPolicy; import com.azure.communication.common.CommunicationUserIdentifier; import com.azure.communication.common.implementation.CommunicationConnectionString; @@ -70,7 +71,7 @@ public final class CallAutomationClientBuilder implements private final ClientLogger logger = new ClientLogger(CallAutomationClientBuilder.class); private String connectionString; private String endpoint; - private String pmaEnpoint; + private String pmaEndpoint; private String hostName; private AzureKeyCredential azureKeyCredential; private TokenCredential tokenCredential; @@ -111,7 +112,7 @@ public CallAutomationClientBuilder endpoint(String endpoint) { * @return CallAutomationClientBuilder object. */ public CallAutomationClientBuilder pmaEndpoint(String pmaEndpoint) { - this.pmaEnpoint = Objects.requireNonNull(pmaEndpoint, "'pmaEndpoint' cannot be null."); + this.pmaEndpoint = Objects.requireNonNull(pmaEndpoint, "'pmaEndpoint' cannot be null."); return this; } @@ -400,8 +401,8 @@ private AzureCommunicationCallAutomationServiceImpl createServiceImpl() { AzureCommunicationCallAutomationServiceImplBuilder clientBuilder = new AzureCommunicationCallAutomationServiceImplBuilder(); - if (pmaEnpoint != null) { - clientBuilder.endpoint(pmaEnpoint).pipeline(builderPipeline); + if (pmaEndpoint != null) { + clientBuilder.endpoint(pmaEndpoint).pipeline(builderPipeline); } else { clientBuilder.endpoint(endpoint).pipeline(builderPipeline); } @@ -430,15 +431,14 @@ private List createHttpPipelineAuthPolicies(boolean isCustom List pipelinePolicies = new ArrayList<>(); if (tokenCredential != null) { - if (pmaEnpoint != null) { + if (pmaEndpoint != null) { pipelinePolicies.add(new CustomBearerTokenAuthenticationPolicy(tokenCredential, endpoint, "https://communication.azure.com//.default")); } else { - pipelinePolicies.add(new BearerTokenAuthenticationPolicy(tokenCredential, - "https://communication.azure.com//.default")); - Map httpHeaders = new HashMap<>(); - httpHeaders.put("x-ms-host", hostName); - pipelinePolicies.add(new AddHeadersPolicy(new HttpHeaders(httpHeaders))); + pipelinePolicies.add(new BearerTokenAuthenticationPolicy(tokenCredential, "https://communication.azure.com//.default")); } + Map httpHeaders = new HashMap<>(); + httpHeaders.put("x-ms-host", hostName); + pipelinePolicies.add(new AddHeadersPolicy(new HttpHeaders(httpHeaders))); } else if (azureKeyCredential != null) { if (isCustomEndpointUsed) { String acsEndpoint = (new CommunicationConnectionString(connectionString)).getEndpoint(); diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/implementation/CustomBearerTokenAuthenticationPolicy.java similarity index 99% rename from sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java rename to sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/implementation/CustomBearerTokenAuthenticationPolicy.java index 5ba820fc5a97..b4899ffb41fc 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CustomBearerTokenAuthenticationPolicy.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/implementation/CustomBearerTokenAuthenticationPolicy.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.communication.callautomation; +package com.azure.communication.callautomation.implementation; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential;