Skip to content
This repository was archived by the owner on Jun 30, 2023. It is now read-only.

Commit 7374cbd

Browse files
Clément Denistangiel
authored andcommitted
Extract auth scope description logic to its own class
- follows the schema repository pattern already used in DiscoveryGenerator - could be reused for swagger generation (if / when supporting accessToken security definition) - added some tests for proper merging of existing scope declarations
1 parent c82a4dc commit 7374cbd

File tree

6 files changed

+102
-41
lines changed

6 files changed

+102
-41
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.google.api.server.spi.config.model;
2+
3+
import com.google.api.server.spi.Constant;
4+
import com.google.api.server.spi.config.scope.AuthScopeExpression;
5+
import com.google.api.server.spi.config.scope.AuthScopeExpressions;
6+
import com.google.api.server.spi.discovery.DiscoveryGenerator;
7+
import com.google.common.base.MoreObjects;
8+
import com.google.common.collect.ImmutableMap;
9+
import com.google.common.collect.Maps;
10+
import com.google.common.io.Resources;
11+
import java.io.IOException;
12+
import java.io.InputStream;
13+
import java.net.URL;
14+
import java.util.Properties;
15+
import java.util.SortedMap;
16+
import java.util.TreeMap;
17+
18+
/**
19+
* Stores a list of OAuth2 scopes with their corresponding descriptions.
20+
* Loads Google scopes from file googleScopeDescriptions.properties in same package.
21+
*/
22+
public class AuthScopeRepository {
23+
24+
private static final ImmutableMap<String, String> GOOGLE_SCOPE_DESCRIPTIONS
25+
= loadScopeDescriptions("googleScopeDescriptions.properties");
26+
27+
private static ImmutableMap<String, String> loadScopeDescriptions(String fileName) {
28+
try {
29+
Properties properties = new Properties();
30+
URL resourceFile = Resources.getResource(DiscoveryGenerator.class, fileName);
31+
InputStream inputStream = resourceFile.openStream();
32+
properties.load(inputStream);
33+
inputStream.close();
34+
return Maps.fromProperties(properties);
35+
} catch (IOException e) {
36+
throw new IllegalStateException("Cannot load scope descriptions from " + fileName, e);
37+
}
38+
}
39+
40+
private final SortedMap<String, String> descriptionsByScope = new TreeMap<>();
41+
42+
public AuthScopeRepository() {
43+
//userinfo.email should always be requested, as it is required for authentication
44+
add(Constant.API_EMAIL_SCOPE);
45+
}
46+
47+
public void add(String scope) {
48+
descriptionsByScope
49+
.put(scope, MoreObjects.firstNonNull(GOOGLE_SCOPE_DESCRIPTIONS.get(scope), scope));
50+
}
51+
52+
public void add(AuthScopeExpression scopeExpression) {
53+
for (String scope : AuthScopeExpressions.encode(scopeExpression)) {
54+
add(scope);
55+
}
56+
}
57+
58+
/**
59+
* Returns the added scopes and their descriptions.
60+
* Unknown scopes will have the scope itself as description.
61+
*
62+
* @return a sorted map containing scopes as key, descriptions as value
63+
*/
64+
public SortedMap<String, String> getDescriptionsByScope() {
65+
return descriptionsByScope;
66+
}
67+
68+
}

endpoints-framework/src/main/java/com/google/api/server/spi/discovery/DiscoveryGenerator.java

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import com.fasterxml.jackson.databind.ObjectMapper;
1919
import com.google.api.client.util.Preconditions;
20-
import com.google.api.server.spi.Constant;
2120
import com.google.api.server.spi.ObjectMapperUtil;
2221
import com.google.api.server.spi.Strings;
2322
import com.google.api.server.spi.TypeLoader;
@@ -33,7 +32,9 @@
3332
import com.google.api.server.spi.config.model.Schema;
3433
import com.google.api.server.spi.config.model.Schema.Field;
3534
import com.google.api.server.spi.config.model.SchemaRepository;
35+
import com.google.api.server.spi.config.model.AuthScopeRepository;
3636
import com.google.api.server.spi.config.model.StandardParameters;
37+
import com.google.api.server.spi.config.scope.AuthScopeExpression;
3738
import com.google.api.server.spi.config.scope.AuthScopeExpressions;
3839
import com.google.api.services.discovery.model.DirectoryList;
3940
import com.google.api.services.discovery.model.DirectoryList.Items;
@@ -49,7 +50,6 @@
4950
import com.google.api.services.discovery.model.RestResource;
5051
import com.google.auto.value.AutoValue;
5152
import com.google.common.base.Function;
52-
import com.google.common.base.MoreObjects;
5353
import com.google.common.base.Splitter;
5454
import com.google.common.collect.ImmutableList;
5555
import com.google.common.collect.ImmutableListMultimap;
@@ -60,21 +60,16 @@
6060
import com.google.common.collect.Lists;
6161
import com.google.common.collect.Maps;
6262
import com.google.common.collect.Multimaps;
63-
import com.google.common.io.Resources;
6463
import com.google.common.reflect.TypeToken;
65-
import java.io.IOException;
66-
import java.io.InputStream;
6764
import java.net.MalformedURLException;
6865
import java.net.URL;
6966
import java.util.ArrayList;
7067
import java.util.Collection;
7168
import java.util.LinkedHashMap;
7269
import java.util.List;
7370
import java.util.Map;
74-
import java.util.Properties;
75-
import java.util.Set;
71+
import java.util.Map.Entry;
7672
import java.util.TreeMap;
77-
import java.util.TreeSet;
7873

7974
/**
8075
* Generates discovery documents without contacting the discovery generator service.
@@ -92,20 +87,6 @@ public class DiscoveryGenerator {
9287
.setKind("discovery#restDescription")
9388
.setParameters(createStandardParameters())
9489
.setProtocol("rest");
95-
private static final Map<String, String> SCOPE_DESCRIPTIONS = loadScopeDescriptions();
96-
97-
private static Map<String, String> loadScopeDescriptions() {
98-
try {
99-
Properties properties = new Properties();
100-
URL resourceFile = Resources.getResource(DiscoveryGenerator.class, "scopeDescriptions.properties");
101-
InputStream inputStream = resourceFile.openStream();
102-
properties.load(inputStream);
103-
inputStream.close();
104-
return Maps.fromProperties(properties);
105-
} catch (IOException e) {
106-
throw new IllegalStateException("Cannot load scope descriptions", e);
107-
}
108-
}
10990

11091
private final TypeLoader typeLoader;
11192

@@ -148,7 +129,7 @@ public Result writeDiscovery(
148129
}
149130

150131
private RestDescription writeApi(ApiKey apiKey, Iterable<ApiConfig> apiConfigs,
151-
DiscoveryContext context, SchemaRepository repo) {
132+
DiscoveryContext context, SchemaRepository schemaRepo) {
152133
// The first step is to scan all methods and try to extract a base path, aka a common prefix
153134
// for all methods. This prefix must end in a slash and can't contain any path parameters.
154135
String servicePath = computeApiServicePath(apiConfigs);
@@ -161,10 +142,8 @@ private RestDescription writeApi(ApiKey apiKey, Iterable<ApiConfig> apiConfigs,
161142
.setRootUrl(context.getApiRoot() + "/")
162143
.setServicePath(servicePath)
163144
.setVersion(apiKey.getVersion());
164-
//stores scopes for all ApiConfigs and ApiMethodConfig, sorted alphabetically
165-
Set<String> allScopes = new TreeSet<>();
166-
//userinfo.email should always be requested, as it is required for authentication
167-
allScopes.add(Constant.API_EMAIL_SCOPE);
145+
146+
final AuthScopeRepository scopeRepo = new AuthScopeRepository();
168147

169148
for (ApiConfig config : apiConfigs) {
170149
// API descriptions should be identical across all configs, but the last one will take
@@ -193,22 +172,21 @@ private RestDescription writeApi(ApiKey apiKey, Iterable<ApiConfig> apiConfigs,
193172
if (config.getCanonicalName() != null) {
194173
doc.setCanonicalName(config.getCanonicalName());
195174
}
196-
allScopes.addAll(AuthScopeExpressions.encode(config.getScopeExpression()));
175+
scopeRepo.add(config.getScopeExpression());
197176
for (ApiMethodConfig methodConfig : config.getApiClassConfig().getMethods().values()) {
198177
if (!methodConfig.isIgnored()) {
199-
writeApiMethod(config, servicePath, doc, methodConfig, repo, allScopes);
178+
writeApiMethod(config, servicePath, doc, methodConfig, schemaRepo, scopeRepo);
200179
}
201180
}
202181
}
203182

204-
LinkedHashMap<String, ScopesElement> scopeElements = new LinkedHashMap<>();
205-
for (String scope : allScopes) {
206-
scopeElements.put(scope, new ScopesElement().setDescription(
207-
MoreObjects.firstNonNull(SCOPE_DESCRIPTIONS.get(scope), scope)));
183+
Map<String, ScopesElement> scopeElements = new LinkedHashMap<>();
184+
for (Entry<String, String> entry : scopeRepo.getDescriptionsByScope().entrySet()) {
185+
scopeElements.put(entry.getKey(), new ScopesElement().setDescription(entry.getValue()));
208186
}
209187
doc.setAuth(new Auth().setOauth2(new Oauth2().setScopes(scopeElements)));
210188

211-
List<Schema> schemas = repo.getAllSchemaForApi(apiKey);
189+
List<Schema> schemas = schemaRepo.getAllSchemaForApi(apiKey);
212190
if (!schemas.isEmpty()) {
213191
Map<String, JsonSchema> docSchemas = Maps.newTreeMap();
214192
for (Schema schema : schemas) {
@@ -220,18 +198,18 @@ private RestDescription writeApi(ApiKey apiKey, Iterable<ApiConfig> apiConfigs,
220198
}
221199

222200
private void writeApiMethod(ApiConfig config, String servicePath, RestDescription doc,
223-
ApiMethodConfig methodConfig, SchemaRepository repo, Set<String> allScopes) {
201+
ApiMethodConfig methodConfig, SchemaRepository schemaRepo, AuthScopeRepository scopeRepo) {
224202
List<String> parts = DOT_SPLITTER.splitToList(methodConfig.getFullMethodName());
225203
Map<String, RestMethod> methods = getMethodMapFromDoc(doc, parts);
226204
Map<String, JsonSchema> parameters = convertMethodParameters(methodConfig);
227-
List<String> scopes = AuthScopeExpressions.encodeMutable(methodConfig.getScopeExpression());
205+
AuthScopeExpression scopeExpression = methodConfig.getScopeExpression();
228206
RestMethod method = new RestMethod()
229207
.setDescription(methodConfig.getDescription())
230208
.setHttpMethod(methodConfig.getHttpMethod())
231209
.setId(methodConfig.getFullMethodName())
232210
.setPath(methodConfig.getCanonicalPath().substring(servicePath.length()))
233-
.setScopes(scopes);
234-
allScopes.addAll(scopes);
211+
.setScopes(AuthScopeExpressions.encodeMutable(scopeExpression));
212+
scopeRepo.add(scopeExpression);
235213
List<String> parameterOrder = computeParameterOrder(methodConfig);
236214
if (!parameterOrder.isEmpty()) {
237215
method.setParameterOrder(parameterOrder);
@@ -242,13 +220,13 @@ private void writeApiMethod(ApiConfig config, String servicePath, RestDescriptio
242220
ApiParameterConfig requestParamConfig = getAndCheckMethodRequestResource(methodConfig);
243221
if (requestParamConfig != null) {
244222
TypeToken<?> requestType = requestParamConfig.getSchemaBaseType();
245-
Schema schema = repo.getOrAdd(requestType, config);
223+
Schema schema = schemaRepo.getOrAdd(requestType, config);
246224
method.setRequest(new Request().set$ref(schema.name()).setParameterName("resource"));
247225
}
248226
if (methodConfig.hasResourceInResponse()) {
249227
TypeToken<?> returnType =
250228
ApiAnnotationIntrospector.getSchemaType(methodConfig.getReturnType(), config);
251-
Schema schema = repo.getOrAdd(returnType, config);
229+
Schema schema = schemaRepo.getOrAdd(returnType, config);
252230
method.setResponse(new Response().set$ref(schema.name()));
253231
}
254232
methods.put(parts.get(parts.size() - 1), method);

endpoints-framework/src/main/resources/com/google/api/server/spi/discovery/scopeDescriptions.properties renamed to endpoints-framework/src/main/resources/com/google/api/server/spi/discovery/googleScopeDescriptions.properties

File renamed without changes.

endpoints-framework/src/test/java/com/google/api/server/spi/discovery/AuthScopeDescriptions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
/**
2222
* Fetches up-to-date auth scope descriptions from https://developers.google.com/identity/protocols/googlescopes.
23-
* Use this to update the scopeDescriptions.properties.
23+
* Use this to update the googleScopeDescriptions.properties.
2424
* Uses Jsoup from the appengine-api-stubs package (used as test dependency).
2525
*/
2626
public class AuthScopeDescriptions {

endpoints-framework/src/test/resources/com/google/api/server/spi/discovery/custom_scopes.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
},
2020
"openid": {
2121
"description": "Authenticate using OpenID Connect"
22+
},
23+
"profile": {
24+
"description": "View your basic profile info"
2225
}
2326
}
2427
}
@@ -105,6 +108,16 @@
105108
"scopes": [
106109
"https://mail.google.com/"
107110
]
111+
},
112+
"multipleScopes": {
113+
"httpMethod": "POST",
114+
"id": "customScopes.customScopesEndpoint.multipleScopes",
115+
"path": "multipleScopes",
116+
"scopes": [
117+
"email",
118+
"profile",
119+
"https://mail.google.com/"
120+
]
108121
}
109122
}
110123
}

test-utils/src/main/java/com/google/api/server/spi/testing/CustomScopesEndpoint.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ public Foo foo() {
2020
public Bar bar() {
2121
return null;
2222
}
23+
@ApiMethod(scopes = {"email", "profile", "https://mail.google.com/"})
24+
public void multipleScopes() {}
2325
}

0 commit comments

Comments
 (0)