Skip to content
Permalink
Browse files
Configure the Graph RBAC API and allow mocking service endpoints
  • Loading branch information
nacx committed Dec 4, 2017
1 parent 86fc88f commit 6472341adcb1a3df0acb0bfc5923dfad80be4dff
Showing 11 changed files with 302 additions and 90 deletions.
@@ -24,6 +24,7 @@
import org.jclouds.azurecompute.arm.features.AvailabilitySetApi;
import org.jclouds.azurecompute.arm.features.DeploymentApi;
import org.jclouds.azurecompute.arm.features.DiskApi;
import org.jclouds.azurecompute.arm.features.GraphRBACApi;
import org.jclouds.azurecompute.arm.features.ImageApi;
import org.jclouds.azurecompute.arm.features.JobApi;
import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
@@ -242,6 +243,15 @@ NetworkSecurityRuleApi getNetworkSecurityRuleApi(@PathParam("resourcegroup") Str
@Delegate
MetricDefinitionsApi getMetricsDefinitionsApi(@PathParam("resourceid") String resourceid);

/**
* The Azure Active Directory Graph API provides programmatic access to Azure
* AD through REST API endpoints.
*
* @see <a href="https://docs.microsoft.com/en-us/rest/api/graphrbac/">docs</a>
*/
@Delegate
GraphRBACApi getGraphRBACApi();

/**
* Returns the information about the current service principal.
*/
@@ -37,11 +37,11 @@
import java.net.URI;
import java.util.Properties;

import org.jclouds.azurecompute.arm.config.AzureComputeHttpApiModule.CurrentServicePrincipal;
import org.jclouds.azurecompute.arm.domain.Region;
import org.jclouds.azurecompute.arm.features.AvailabilitySetApi;
import org.jclouds.azurecompute.arm.features.DeploymentApi;
import org.jclouds.azurecompute.arm.features.DiskApi;
import org.jclouds.azurecompute.arm.features.GraphRBACApi;
import org.jclouds.azurecompute.arm.features.ImageApi;
import org.jclouds.azurecompute.arm.features.LoadBalancerApi;
import org.jclouds.azurecompute.arm.features.LocationApi;
@@ -125,7 +125,7 @@ public static Properties defaultProperties() {
properties.put(API_VERSION_PREFIX + MetricDefinitionsApi.class.getSimpleName(), "2017-05-01-preview");
properties.put(API_VERSION_PREFIX + MetricsApi.class.getSimpleName(), "2016-09-01");
properties.put(API_VERSION_PREFIX + VirtualMachineScaleSetApi.class.getSimpleName(), "2017-03-30");
properties.put(API_VERSION_PREFIX + CurrentServicePrincipal.class.getSimpleName(), "1.6");
properties.put(API_VERSION_PREFIX + GraphRBACApi.class.getSimpleName(), "1.6");

return properties;
}
@@ -16,6 +16,8 @@
*/
package org.jclouds.azurecompute.arm;

import static org.jclouds.reflect.Reflection2.typeToken;

import java.net.URI;
import java.util.Properties;

@@ -32,8 +34,6 @@
import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;

import static org.jclouds.reflect.Reflection2.typeToken;

/**
* Implementation of {@link ApiMetadata} for Microsoft Azure Resource Manager REST API
*/
@@ -43,9 +43,13 @@ public class AzureManagementApiMetadata extends BaseHttpApiMetadata<AzureCompute
public Builder toBuilder() {
return new Builder().fromApiMetadata(this);
}

public static Builder builder() {
return new Builder();
}

public AzureManagementApiMetadata() {
this(new Builder());
this(builder());
}

protected AzureManagementApiMetadata(final Builder builder) {
@@ -17,7 +17,6 @@
package org.jclouds.azurecompute.arm.config;

import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.rest.config.BinderUtils.bindHttpApi;

import java.net.URI;
import java.util.concurrent.TimeUnit;
@@ -26,14 +25,10 @@
import java.util.regex.Pattern;

import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;

import org.jclouds.azurecompute.arm.AzureComputeApi;
import org.jclouds.azurecompute.arm.config.GraphRBAC.GraphRBACForTenant;
import org.jclouds.azurecompute.arm.domain.ServicePrincipal;
import org.jclouds.azurecompute.arm.filters.ApiVersionFilter;
import org.jclouds.azurecompute.arm.handlers.AzureComputeErrorHandler;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.annotation.ClientError;
@@ -43,28 +38,22 @@
import org.jclouds.location.suppliers.implicit.FirstRegion;
import org.jclouds.oauth.v2.config.OAuthConfigFactory;
import org.jclouds.oauth.v2.config.OAuthScopes;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ConfiguresHttpApi;
import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.OnlyElement;
import org.jclouds.rest.annotations.QueryParams;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.config.HttpApiModule;
import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier;

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;

@ConfiguresHttpApi
public class AzureComputeHttpApiModule extends HttpApiModule<AzureComputeApi> {

private static final Pattern OAUTH_TENANT_PATTERN = Pattern
.compile("https://login.microsoftonline.com/([^/]+)/oauth2/token");
.compile("https://login.microsoft(?:online)?.com/([^/]+)/oauth2/token");

@Override
protected void bindErrorHandlers() {
@@ -82,15 +71,20 @@ protected void installLocations() {
@Override
protected void configure() {
super.configure();
bindHttpApi(binder(), CurrentServicePrincipal.class);
bind(OAuthScopes.class).toInstance(OAuthScopes.NoScopes.create());
bind(OAuthConfigFactory.class).to(AzureOAuthConfigFactory.class).in(Scopes.SINGLETON);
bindServiceEndpoints();
}

protected void bindServiceEndpoints() {
bind(new TypeLiteral<Supplier<URI>>() {
}).annotatedWith(GraphRBAC.class).to(GraphRBACForTenant.class).in(Scopes.SINGLETON);
}

@Provides
@Singleton
@Tenant
protected String provideTenant(@Named("oauth.endpoint") final String oauthEndpoint) {
protected final String provideTenant(@Named("oauth.endpoint") final String oauthEndpoint) {
Matcher m = OAUTH_TENANT_PATTERN.matcher(oauthEndpoint);
if (!m.matches()) {
throw new IllegalArgumentException("Could not parse tenantId from: " + oauthEndpoint);
@@ -100,37 +94,15 @@ protected String provideTenant(@Named("oauth.endpoint") final String oauthEndpoi

@Provides
@Singleton
@GraphRBAC
protected Supplier<URI> graphRBACEndpoint(@Tenant String tenantId) {
return Suppliers.ofInstance(URI.create(GraphRBAC.ENDPOINT + tenantId));
}

@Provides
@Singleton
protected Supplier<ServicePrincipal> provideServicePrincipal(final CurrentServicePrincipal currentServicePrincipal,
protected final Supplier<ServicePrincipal> provideServicePrincipal(final AzureComputeApi api,
AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds) {
// This supplier must be defensive against any auth exception.
return MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException,
new Supplier<ServicePrincipal>() {
@Override
public ServicePrincipal get() {
return currentServicePrincipal.get();
return api.getGraphRBACApi().getCurrentServicePrincipal();
}
}, seconds, TimeUnit.SECONDS);
}

@RequestFilters({ OAuthFilter.class, ApiVersionFilter.class })
@Consumes(MediaType.APPLICATION_JSON)
@Endpoint(GraphRBAC.class)
@OAuthResource(GraphRBAC.ENDPOINT)
public interface CurrentServicePrincipal {

@Named("servicePrincipal:get")
@GET
@Path("/servicePrincipals")
@QueryParams(keys = "$filter", values = "appId eq '{jclouds.identity}'")
@SelectJson("value")
@OnlyElement
ServicePrincipal get();
}
}
@@ -20,9 +20,13 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URI;

import javax.inject.Inject;
import javax.inject.Qualifier;

import com.google.common.base.Supplier;

/**
* Provides the Graph RBAC API endpoint for the current tenant.
*/
@@ -32,4 +36,19 @@
public @interface GraphRBAC {

String ENDPOINT = "https://graph.windows.net/";

static class GraphRBACForTenant implements Supplier<URI> {
private final String tenantId;

@Inject
GraphRBACForTenant(@Tenant String tenantId) {
this.tenantId = tenantId;
}

@Override
public URI get() {
return URI.create(GraphRBAC.ENDPOINT + tenantId);
}

}
}
@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.azurecompute.arm.features;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;

import org.jclouds.azurecompute.arm.config.GraphRBAC;
import org.jclouds.azurecompute.arm.config.OAuthResource;
import org.jclouds.azurecompute.arm.domain.ServicePrincipal;
import org.jclouds.azurecompute.arm.filters.ApiVersionFilter;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.OnlyElement;
import org.jclouds.rest.annotations.QueryParams;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.SelectJson;

import com.google.inject.name.Named;

@RequestFilters({ OAuthFilter.class, ApiVersionFilter.class })
@Consumes(MediaType.APPLICATION_JSON)
@Endpoint(GraphRBAC.class)
@OAuthResource(GraphRBAC.ENDPOINT)
public interface GraphRBACApi {

@Named("servicePrincipal:get")
@GET
@Path("/servicePrincipals")
@QueryParams(keys = "$filter", values = "appId eq '{jclouds.identity}'")
@SelectJson("value")
@OnlyElement
ServicePrincipal getCurrentServicePrincipal();
}
@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.azurecompute.arm.config;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;

import org.testng.annotations.Test;

@Test(groups = "unit", testName = "ParseTenantIdTest")
public class ParseTenantIdTest {

@Test
public void testParseTenantId() {
AzureComputeHttpApiModule module = new AzureComputeHttpApiModule();

assertEquals(module.provideTenant("https://login.microsoftonline.com/tenantId/oauth2/token"), "tenantId");
assertEquals(module.provideTenant("https://login.microsoft.com/tenant2/oauth2/token"), "tenant2");

assertInvalid(module, "https://login.microsoftonline.com/a/b/c/oauth2/token");
assertInvalid(module, "https://login.microsoft.com/a/b/c/oauth2/token");
assertInvalid(module, "https://login.microsoftonline.com//oauth2/token");
assertInvalid(module, "https://login.microsoft.com//oauth2/token");
assertInvalid(module, "https://login.microsoftabc.com/tenant/oauth2/token");
}

private static void assertInvalid(AzureComputeHttpApiModule module, String endpoint) {
try {
module.provideTenant(endpoint);
fail("Expected an IllegalArgumentException for endpoint: " + endpoint);
} catch (IllegalArgumentException ex) {
assertEquals(ex.getMessage(), "Could not parse tenantId from: " + endpoint);
}
}

}
@@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.azurecompute.arm.features;

import static org.testng.Assert.assertEquals;

import java.io.IOException;

import org.jclouds.azurecompute.arm.domain.ServicePrincipal;
import org.jclouds.azurecompute.arm.internal.BaseAzureComputeApiMockTest;
import org.testng.annotations.Test;

@Test(groups = "unit", testName = "GraphRBACApiMockTest", singleThreaded = true)
public class GraphRBACApiMockTest extends BaseAzureComputeApiMockTest {

public void testGetCurrentServicePrincipal() throws IOException, InterruptedException {
server.enqueue(jsonResponse("/serviceprincipals.json"));

ServicePrincipal sp = api.getGraphRBACApi().getCurrentServicePrincipal();

assertEquals(sp.appId(), "applicationId");
assertSent(server, "GET", "/graphrbac/tenant-id/servicePrincipals?$filter=appId%20eq%20%27mock%27&api-version=1.6");
}

}

0 comments on commit 6472341

Please sign in to comment.