diff --git a/indexer-service-azure/pom.xml b/indexer-service-azure/pom.xml index 3772d0ac533700d87fcf4bc514150f3af6a95700..9300259d9eb597dea2e42a6a087be24ca37cf0f2 100644 --- a/indexer-service-azure/pom.xml +++ b/indexer-service-azure/pom.xml @@ -34,6 +34,17 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> + <dependency> + <groupId>org.springframework.security.oauth</groupId> + <artifactId>spring-security-oauth2</artifactId> + <version>2.3.6.RELEASE</version> + </dependency> + + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-jwt</artifactId> + <version>1.0.10.RELEASE</version> + </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> @@ -69,13 +80,11 @@ <version>1.0-SNAPSHOT</version> </dependency> - <dependency> <groupId>com.microsoft.azure</groupId> - <artifactId>azure-active-directory-spring-boot-starter</artifactId> - <version>${azure.version}</version> + <artifactId>msal4j</artifactId> + <version>0.5.0-preview</version> </dependency> - <!-- Key vault dependency--> <dependency> <groupId>com.microsoft.azure</groupId> @@ -85,6 +94,7 @@ <dependency> <groupId>com.microsoft.azure</groupId> <artifactId>azure-keyvault</artifactId> + <version>1.2.2</version> <exclusions> <exclusion> <groupId>com.microsoft.azure</groupId> @@ -95,6 +105,7 @@ <dependency> <groupId>com.microsoft.azure</groupId> <artifactId>azure-client-authentication</artifactId> + <version>1.6.12</version> </dependency> <!-- end KeyVault dependencies--> </dependencies> diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/IndexerAzureApplication.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/IndexerAzureApplication.java index 5620efb05156ee7dfe732ed669055d8f5e231b5b..a758ed0b883be45b9f917f65e13c80ddca939ef8 100644 --- a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/IndexerAzureApplication.java +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/IndexerAzureApplication.java @@ -7,7 +7,8 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfi import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -@SpringBootApplication(exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class }) +//@SpringBootApplication(exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class }) +@SpringBootApplication @Configuration @ComponentScan({"org.opendes.core", "org.opendes.indexer"}) public class IndexerAzureApplication { diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/security/msal4j/ApiController.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/security/msal4j/ApiController.java new file mode 100644 index 0000000000000000000000000000000000000000..8f2ed035998ff3530d635f35b209b194d71128d6 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/security/msal4j/ApiController.java @@ -0,0 +1,45 @@ +package org.opendes.indexer.azure.security.msal4j; + +import javax.servlet.http.HttpServletRequest; +import org.opendes.indexer.azure.util.ServiceAccountJwtClientImpl; +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.http.*; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.client.RestTemplate; + +@Controller +public class ApiController { + + @Autowired + ServiceAccountJwtClientImpl service; + + + @RequestMapping("/obo_api") + public ResponseEntity<String> callOboApi(HttpServletRequest httpRequest) throws Throwable { + + String token = service.getIdToken("tenant1"); + + String oboApiCallRes = callOboService(token); + + return new ResponseEntity<>(HttpStatus.OK); + } + + private String callOboService(String accessToken){ + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + headers.set("Authorization", "Bearer " + accessToken); + + HttpEntity<String> entity = new HttpEntity<>(null, headers); + + String result = restTemplate.exchange("http://localhost:8081/api", HttpMethod.GET, + entity, String.class).getBody(); + + return result; + } +} + diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/security/msal4j/BasicConfiguration.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/security/msal4j/BasicConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..ac1c8400f9b08240b8ce0a7fcfa6f25369fdfef5 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/security/msal4j/BasicConfiguration.java @@ -0,0 +1,26 @@ +package org.opendes.indexer.azure.security.msal4j; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Getter +@Setter +@Component +@ConfigurationProperties("aad") +public class BasicConfiguration { + String clientId; + String authority; + String redirectUri; + String secretKey; + String oboApi; + + public String getAuthority(){ + if (!authority.endsWith("/")) { + authority += "/"; + } + return authority; + } +} \ No newline at end of file diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java index 61359097e4f6b1f990d4d68f92c56207f3af490d..bc33ea7acb7890468090570a13ecc0f7fbca9e4f 100644 --- a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java @@ -2,23 +2,11 @@ package org.opendes.indexer.azure.util; import com.auth0.jwt.JWT; import com.auth0.jwt.exceptions.JWTDecodeException; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.microsoft.aad.adal4j.AuthenticationContext; import com.microsoft.aad.adal4j.AuthenticationResult; import com.microsoft.aad.adal4j.ClientCredential; -import com.nimbusds.oauth2.sdk.GrantType; -import org.apache.http.HttpHeaders; +import com.microsoft.aad.msal4j.*; import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ContentType; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; import org.opendes.client.api.DpsHeaders; import org.opendes.client.multitenancy.ITenantFactory; import org.opendes.client.multitenancy.TenantInfo; @@ -29,35 +17,18 @@ import org.opendes.core.util.AppException; import org.opendes.core.util.IHeadersInfo; import org.opendes.core.util.IServiceAccountJwtClient; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.opendes.indexer.azure.security.msal4j.BasicConfiguration; + +import javax.naming.ServiceUnavailableException; import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import java.util.*; +import java.util.concurrent.*; @Component public class ServiceAccountJwtClientImpl implements IServiceAccountJwtClient { - @Value("${Auth_URL}") - private String Auth_URL; - - @Value("${azure.activedirectory.client-id}") - private String client_id; - - @Value("${azure.activedirectory.client-secret}") - private String client_secret; - - @Value("${azure.activedirectory.AppIdUri}") - private String resource; - - @Autowired private ITenantFactory tenantInfoServiceProvider; @Autowired @@ -66,51 +37,110 @@ public class ServiceAccountJwtClientImpl implements IServiceAccountJwtClient { private JwtCache cacheService; @Autowired private JaxRsDpsLog log; + @Autowired + private BasicConfiguration configuration; +// +// public String getIdToken(String tenantName) { +// this.log.info("Tenant name received for auth token is: " + tenantName); +// TenantInfo tenant = this.tenantInfoServiceProvider.getTenantInfo(tenantName); +// if (tenant == null) { +// this.log.error("Invalid tenant name receiving from azure"); +// throw new AppException(HttpStatus.SC_BAD_REQUEST, "Invalid tenant Name", "Invalid tenant Name from azure"); +// } +// String ACCESS_TOKEN = ""; +// try { +// +// IdToken cachedToken = this.cacheService.get(tenant.getServiceAccount()); +// this.headersInfoAzure.getHeaders().put(DpsHeaders.USER_EMAIL, tenant.getServiceAccount()); +// +// if (!IdToken.refreshToken(cachedToken)) { +// ACCESS_TOKEN = cachedToken.getTokenValue(); +// } +// +// ExecutorService service = Executors.newFixedThreadPool(1); +// AuthenticationContext context = null; +// +// try { +// context = new AuthenticationContext(configuration.getAuthority(), false, service); +// ClientCredential credential = new ClientCredential(configuration.getClientId(), configuration.getSecretKey()); +// Future<AuthenticationResult> future = context.acquireToken(configuration.getOboApi(), credential, null); +// +// ACCESS_TOKEN = future.get().getAccessToken(); +// +// if (future == null) { +// log.error(String.format("Azure Authentication: %s", future.get().getAccessToken())); +// throw new AppException(HttpStatus.SC_FORBIDDEN, "Access denied", "The user is not authorized to perform this action"); +// } +// IdToken idToken = IdToken.builder().tokenValue(ACCESS_TOKEN).expirationTimeMillis(JWT.decode(ACCESS_TOKEN).getExpiresAt().getTime()).build(); +// +// this.cacheService.put(tenant.getServiceAccount(), idToken); +// +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } catch (ExecutionException e) { +// e.printStackTrace(); +// } catch (MalformedURLException e) { +// e.printStackTrace(); +// } finally { +// service.shutdown(); +// } +// } catch (JWTDecodeException e) { +// throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Persistence error", "Invalid token, error decoding", e); +// } catch (AppException e) { +// throw e; +// } catch (Exception e) { +// throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Persistence error", "Error generating token", e); +// } +// +// return ACCESS_TOKEN; +// } public String getIdToken(String tenantName) { + this.log.info("Tenant name received for auth token is: " + tenantName); TenantInfo tenant = this.tenantInfoServiceProvider.getTenantInfo(tenantName); if (tenant == null) { this.log.error("Invalid tenant name receiving from azure"); throw new AppException(HttpStatus.SC_BAD_REQUEST, "Invalid tenant Name", "Invalid tenant Name from azure"); } - String ACCESS_TOKEN = ""; + + String authToken = ""; + IAuthenticationResult updatedResult; try { IdToken cachedToken = this.cacheService.get(tenant.getServiceAccount()); this.headersInfoAzure.getHeaders().put(DpsHeaders.USER_EMAIL, tenant.getServiceAccount()); if (!IdToken.refreshToken(cachedToken)) { - ACCESS_TOKEN = cachedToken.getTokenValue(); + authToken = cachedToken.getTokenValue(); + return authToken; } - ExecutorService service = Executors.newFixedThreadPool(1); - AuthenticationContext context = null; - - try { - context = new AuthenticationContext(Auth_URL, false, service); - ClientCredential credential = new ClientCredential(client_id, client_secret); - Future<AuthenticationResult> future = context.acquireToken(resource, credential, null); + ConfidentialClientApplication application = ConfidentialClientApplication.builder( + configuration.getClientId(), + ClientCredentialFactory.create(configuration.getSecretKey())) + .authority(configuration.getAuthority()) + .build(); - ACCESS_TOKEN = future.get().getAccessToken(); + SilentParameters silentParameters = + SilentParameters.builder(Collections.singleton(configuration.getOboApi())) + .build(); + CompletableFuture<IAuthenticationResult> auth = application.acquireTokenSilently(silentParameters); - if (future == null) { - log.error(String.format("Azure Authentication: %s", future.get().getAccessToken())); - throw new AppException(HttpStatus.SC_FORBIDDEN, "Access denied", "The user is not authorized to perform this action"); - } - IdToken idToken = IdToken.builder().tokenValue(ACCESS_TOKEN).expirationTimeMillis(JWT.decode(ACCESS_TOKEN).getExpiresAt().getTime()).build(); + updatedResult = auth.join(); - this.cacheService.put(tenant.getServiceAccount(), idToken); - - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } catch (MalformedURLException e) { - e.printStackTrace(); - } finally { - service.shutdown(); + if (updatedResult == null) { + throw new ServiceUnavailableException("authentication result was null"); } +// if (updatedResult == null){ +// OnBehalfOfParameters parameters = +// OnBehalfOfParameters.builder(Collections.singleton(configuration.getOboApi()), +// new UserAssertion(authToken)) +// .build(); +// +// updatedResult = application.acquireToken(parameters).join(); +// } + authToken = updatedResult.accessToken(); } catch (JWTDecodeException e) { throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Persistence error", "Invalid token, error decoding", e); } catch (AppException e) { @@ -119,60 +149,6 @@ public class ServiceAccountJwtClientImpl implements IServiceAccountJwtClient { throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Persistence error", "Error generating token", e); } - return ACCESS_TOKEN; - } -/* - private String getAccessTokenByRequest(){ - //prepare auth request - Map<String, Object> requestParam = this.getParameterHeaders(); - - TokenRequest tokenRequest = new TokenRequest(GrantType.CLIENT_CREDENTIALS); - tokenRequest.setPayload(JSON_FACTORY.toString(requestParam)); - - String serviceAccountName = String.format(SERVICE_ACCOUNT_NAME_FORMAT, tenant.getProjectId(), tenant.getServiceAccount()); - - Iam.Projects.ServiceAccounts.SignJwt signJwt = this.getIam().projects().serviceAccounts().signJwt(serviceAccountName, signJwtRequest); - SignJwtResponse signJwtResponse = signJwt.execute(); - String signedJwt = signJwtResponse.getSignedJwt(); - - // Getting id token - List<NameValuePair> postParameters = new ArrayList<>(); - postParameters.add(new BasicNameValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")); - postParameters.add(new BasicNameValuePair("assertion", signedJwt)); - - HttpPost post = new HttpPost(JWT_AUDIENCE); - post.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); - post.setEntity(new UrlEncodedFormEntity(postParameters, "UTF-8")); - - try(CloseableHttpClient httpclient = HttpClients.createDefault(); - CloseableHttpResponse httpResponse = httpclient.execute(post)) { - JsonObject jsonContent = new JsonParser().parse(EntityUtils.toString(httpResponse.getEntity())).getAsJsonObject(); - - if (!jsonContent.has("id_token")) { - log.error(String.format("Google IAM response: %s", jsonContent.toString())); - throw new AppException(HttpStatus.SC_FORBIDDEN, "Access denied", "The user is not authorized to perform this action"); - } - - String token = jsonContent.get("id_token").getAsString(); - IdToken idToken = IdToken.builder().tokenValue(token).expirationTimeMillis(JWT.decode(token).getExpiresAt().getTime()).build(); - - this.cacheService.put(tenant.getServiceAccount(), idToken); - - return token; - } - } - - private Map<String, Object> getParameterHeaders() { - - Map<String, Object> param = new HashMap<>(); - - param.put("grant_type", GRANT_Type); - param.put("client_id", client_id); - param.put("client_secret", client_secret); - param.put("resource", resource ); - - return param; + return authToken; } - - */ } diff --git a/indexer-service-azure/src/main/resources/logback-spring.xml b/indexer-service-azure/src/main/resources/logback-spring.xml index 1b0e57c0fc9bc839239b3933ba2600688a910c69..b6cddae16a051d94a1813eff31295b33f9eefd1d 100644 --- a/indexer-service-azure/src/main/resources/logback-spring.xml +++ b/indexer-service-azure/src/main/resources/logback-spring.xml @@ -15,11 +15,7 @@ limitations under the License. --> -<configuration scan="true"> - - <include resource="org/springframework/boot/logging/logback/base.xml"/> - - <logger name="org.springframework.security" level="DEBUG"/> +<configuration> <appender name="Console" class="ch.qos.logback.core.ConsoleAppender"> diff --git a/indexer-service-root/src/main/java/org/opendes/indexer/middleware/IndexerFilter.java b/indexer-service-root/src/main/java/org/opendes/indexer/middleware/IndexerFilter.java index a268cf382c938b199d34d6c0aab78803aaaf03e7..bc730a1bc50d576f4dcbb315f6ddc815c7639a0f 100644 --- a/indexer-service-root/src/main/java/org/opendes/indexer/middleware/IndexerFilter.java +++ b/indexer-service-root/src/main/java/org/opendes/indexer/middleware/IndexerFilter.java @@ -14,36 +14,36 @@ package org.opendes.indexer.middleware; - -import com.google.api.client.http.HttpMethods; -import com.google.common.base.Strings; - -import org.apache.http.HttpStatus; -import org.opendes.client.api.DpsHeaders; -import org.opendes.core.auth.AuthorizationService; -import org.opendes.core.logging.JaxRsDpsLog; -import org.opendes.core.model.AuthorizationResponse; -import org.opendes.core.util.AppException; -import org.opendes.indexer.util.IRequestInfo; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -import javax.annotation.security.PermitAll; -import javax.annotation.security.RolesAllowed; -import javax.servlet.*; -import javax.servlet.Filter; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.container.ResourceInfo; -import javax.ws.rs.core.Context; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; +// +//import com.google.api.client.http.HttpMethods; +//import com.google.common.base.Strings; +// +//import org.apache.http.HttpStatus; +//import org.opendes.client.api.DpsHeaders; +//import org.opendes.core.auth.AuthorizationService; +//import org.opendes.core.logging.JaxRsDpsLog; +//import org.opendes.core.model.AuthorizationResponse; +//import org.opendes.core.util.AppException; +//import org.opendes.indexer.util.IRequestInfo; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.context.annotation.Lazy; +//import org.springframework.stereotype.Component; +// +//import javax.annotation.security.PermitAll; +//import javax.annotation.security.RolesAllowed; +//import javax.servlet.*; +//import javax.servlet.Filter; +//import javax.servlet.http.HttpServletRequest; +//import javax.ws.rs.container.ResourceInfo; +//import javax.ws.rs.core.Context; +// +//import java.io.IOException; +//import java.lang.reflect.Method; +//import java.util.Arrays; +//import java.util.List; //@Component -public class IndexerFilter { +//public class IndexerFilter { // private static final String DISABLE_AUTH_PROPERTY = "com.slb.indexer.disableAuth"; // private static final String PATH_SWAGGER = "/swagger.json"; @@ -141,4 +141,4 @@ public class IndexerFilter { // } -} \ No newline at end of file +//} \ No newline at end of file