package com.microsoft.aad.adal4j;
import javax.net.ssl.SSLSocketFactory;
import java.net.Proxy;
import java.net.URL;
import java.util.Arrays;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class AuthenticationAuthority {
private final Logger log = LoggerFactory
.getLogger(AuthenticationAuthority.class);
private final static String[] TRUSTED_HOST_LIST = { "login.windows.net",
"login.chinacloudapi.cn", "login-us.microsoftonline.com", "login.microsoftonline.de",
"login.microsoftonline.com", "login.microsoftonline.us" };
private final static String TENANTLESS_TENANT_NAME = "common";
private final static String AUTHORIZE_ENDPOINT_TEMPLATE = "https://{host}/{tenant}/oauth2/authorize";
private final static String DISCOVERY_ENDPOINT = "common/discovery/instance";
private final static String TOKEN_ENDPOINT = "/oauth2/token";
private final static String USER_REALM_ENDPOINT = "common/userrealm";
final static String DEVICE_CODE_ENDPOINT = "/oauth2/devicecode";
private String host;
private String issuer;
private final String instanceDiscoveryEndpointFormat = "https://%s/"
+ DISCOVERY_ENDPOINT;
private final String userRealmEndpointFormat = "https://%s/"
+ USER_REALM_ENDPOINT + "/%s?api-version=1.0";
private final String tokenEndpointFormat = "https://%s/{tenant}"
+ TOKEN_ENDPOINT;
private final String devicecodeEndpointFormat = "https://%s/{tenant}"
+ DEVICE_CODE_ENDPOINT;
private String authority = "https://%s/%s/";
private String instanceDiscoveryEndpoint;
private String tokenEndpoint;
private String deviceCodeEndpoint;
private final AuthorityType authorityType;
private boolean isTenantless;
private String tokenUri;
private String selfSignedJwtAudience;
private boolean instanceDiscoveryCompleted;
private final URL authorityUrl;
private final boolean validateAuthority;
AuthenticationAuthority(final URL authorityUrl,
final boolean validateAuthority) {
this.authorityUrl = authorityUrl;
this.authorityType = detectAuthorityType();
this.validateAuthority = validateAuthority;
validateAuthorityUrl();
setupAuthorityProperties();
}
String getHost() {
return host;
}
String getIssuer() {
return issuer;
}
String getAuthority() {
return authority;
}
String getTokenEndpoint() {
return tokenEndpoint;
}
String getDeviceCodeEndpoint() { return deviceCodeEndpoint; }
String getUserRealmEndpoint(String username) {
return String.format(userRealmEndpointFormat, host, username);
}
AuthorityType getAuthorityType() {
return authorityType;
}
boolean isTenantless() {
return isTenantless;
}
String getTokenUri() {
return tokenUri;
}
String getSelfSignedJwtAudience() {
return selfSignedJwtAudience;
}
void setSelfSignedJwtAudience(final String selfSignedJwtAudience) {
this.selfSignedJwtAudience = selfSignedJwtAudience;
}
void doInstanceDiscovery(final Map<String, String> headers,
final Proxy proxy, final SSLSocketFactory sslSocketFactory)
throws Exception {
if (!instanceDiscoveryCompleted) {
if (!doStaticInstanceDiscovery()) {
if (validateAuthority
&& !doDynamicInstanceDiscovery(headers, proxy,
sslSocketFactory)) {
throw new AuthenticationException(
AuthenticationErrorMessage.AUTHORITY_NOT_IN_VALID_LIST);
}
}
String msg = LogHelper.createMessage(
"Instance discovery was successful",
headers.get(ClientDataHttpHeaders.CORRELATION_ID_HEADER_NAME));
log.info(msg);
instanceDiscoveryCompleted = true;
}
}
boolean doDynamicInstanceDiscovery(final Map<String, String> headers,
final Proxy proxy, final SSLSocketFactory sslSocketFactory)
throws Exception {
final String json = HttpHelper.executeHttpGet(log,
instanceDiscoveryEndpoint, headers, proxy, sslSocketFactory);
final InstanceDiscoveryResponse discoveryResponse = JsonHelper
.convertJsonToObject(json, InstanceDiscoveryResponse.class);
return !StringHelper.isBlank(discoveryResponse
.getTenantDiscoveryEndpoint());
}
boolean doStaticInstanceDiscovery() {
if (validateAuthority) {
return Arrays.asList(TRUSTED_HOST_LIST).contains(this.host);
}
return true;
}
void setupAuthorityProperties() {
final String host = this.authorityUrl.getAuthority().toLowerCase();
final String path = this.authorityUrl.getPath().substring(1)
.toLowerCase();
final String tenant = path.substring(0, path.indexOf("/"))
.toLowerCase();
this.host = host;
this.authority = String.format(this.authority, host, tenant);
this.instanceDiscoveryEndpoint = String.format(
this.instanceDiscoveryEndpointFormat, host);
this.tokenEndpoint = String.format(this.tokenEndpointFormat, host);
this.tokenEndpoint = this.tokenEndpoint.replace("{tenant}", tenant);
this.tokenUri = this.tokenEndpoint;
this.issuer = this.tokenUri;
this.deviceCodeEndpoint = String.format(this.devicecodeEndpointFormat, host);
this.deviceCodeEndpoint = this.deviceCodeEndpoint.replace("{tenant}", tenant);
this.isTenantless = TENANTLESS_TENANT_NAME.equalsIgnoreCase(tenant);
this.setSelfSignedJwtAudience(this.getIssuer());
this.createInstanceDiscoveryEndpoint(tenant);
}
AuthorityType detectAuthorityType() {
if (authorityUrl == null) {
throw new NullPointerException("authority");
}
final String path = authorityUrl.getPath().substring(1);
if (StringHelper.isBlank(path)) {
throw new IllegalArgumentException(
AuthenticationErrorMessage.AUTHORITY_URI_INVALID_PATH);
}
final String firstPath = path.substring(0, path.indexOf("/"));
final AuthorityType authorityType = isAdfsAuthority(firstPath) ? AuthorityType.ADFS
: AuthorityType.AAD;
return authorityType;
}
void validateAuthorityUrl() {
if (authorityType != AuthorityType.AAD && validateAuthority) {
throw new IllegalArgumentException(
AuthenticationErrorMessage.UNSUPPORTED_AUTHORITY_VALIDATION);
}
if (!this.authorityUrl.getProtocol().equalsIgnoreCase("https")) {
throw new IllegalArgumentException(
AuthenticationErrorMessage.AUTHORITY_URI_INSECURE);
}
if (this.authorityUrl.toString().contains("#")) {
throw new IllegalArgumentException(
"authority is invalid format (contains fragment)");
}
if (!StringHelper.isBlank(this.authorityUrl.getQuery())) {
throw new IllegalArgumentException(
"authority cannot contain query parameters");
}
}
void createInstanceDiscoveryEndpoint(final String tenant) {
this.instanceDiscoveryEndpoint += "?api-version=1.0&authorization_endpoint="
+ AUTHORIZE_ENDPOINT_TEMPLATE;
this.instanceDiscoveryEndpoint = this.instanceDiscoveryEndpoint
.replace("{host}", host);
this.instanceDiscoveryEndpoint = this.instanceDiscoveryEndpoint
.replace("{tenant}", tenant);
}
static boolean isAdfsAuthority(final String firstPath) {
return firstPath.compareToIgnoreCase("adfs") == 0;
}
}