... | ... |
@@ -22037,6 +22037,63 @@ |
22037 | 22037 |
"type": "string" |
22038 | 22038 |
}, |
22039 | 22039 |
"description": "RedirectURIs is the valid redirection URIs associated with a client" |
22040 |
+ }, |
|
22041 |
+ "scopeRestrictions": { |
|
22042 |
+ "type": "array", |
|
22043 |
+ "items": { |
|
22044 |
+ "$ref": "v1.ScopeRestriction" |
|
22045 |
+ }, |
|
22046 |
+ "description": "ScopeRestrictions describes which scopes this client can request. Each requested scope is checked against each restriction. If any restriction matches, then the scope is allowed. If no restriction matches, then the scope is denied." |
|
22047 |
+ }, |
|
22048 |
+ "allowAnyScope": { |
|
22049 |
+ "type": "boolean", |
|
22050 |
+ "description": "AllowAnyScope indicates that the client is allowed to request a token unconstrained by scopes. If this is true, then ScopeRestrictions is ignored." |
|
22051 |
+ } |
|
22052 |
+ } |
|
22053 |
+ }, |
|
22054 |
+ "v1.ScopeRestriction": { |
|
22055 |
+ "id": "v1.ScopeRestriction", |
|
22056 |
+ "description": "ScopeRestriction describe one restriction on scopes. Exactly one option must be non-nil.", |
|
22057 |
+ "properties": { |
|
22058 |
+ "literals": { |
|
22059 |
+ "type": "array", |
|
22060 |
+ "items": { |
|
22061 |
+ "type": "string" |
|
22062 |
+ }, |
|
22063 |
+ "description": "ExactValues means the scope has to match a particular set of strings exactly" |
|
22064 |
+ }, |
|
22065 |
+ "clusterRole": { |
|
22066 |
+ "$ref": "v1.ClusterRoleScopeRestriction", |
|
22067 |
+ "description": "ClusterRole describes a set of restrictions for cluster role scoping." |
|
22068 |
+ } |
|
22069 |
+ } |
|
22070 |
+ }, |
|
22071 |
+ "v1.ClusterRoleScopeRestriction": { |
|
22072 |
+ "id": "v1.ClusterRoleScopeRestriction", |
|
22073 |
+ "description": "ClusterRoleScopeRestriction describes restrictions on cluster role scopes", |
|
22074 |
+ "required": [ |
|
22075 |
+ "roleNames", |
|
22076 |
+ "namespaces", |
|
22077 |
+ "allowEscalation" |
|
22078 |
+ ], |
|
22079 |
+ "properties": { |
|
22080 |
+ "roleNames": { |
|
22081 |
+ "type": "array", |
|
22082 |
+ "items": { |
|
22083 |
+ "type": "string" |
|
22084 |
+ }, |
|
22085 |
+ "description": "RoleNames is the list of cluster roles that can referenced. * means anything" |
|
22086 |
+ }, |
|
22087 |
+ "namespaces": { |
|
22088 |
+ "type": "array", |
|
22089 |
+ "items": { |
|
22090 |
+ "type": "string" |
|
22091 |
+ }, |
|
22092 |
+ "description": "Namespaces is the list of namespaces that can be referenced. * means any of them (including *)" |
|
22093 |
+ }, |
|
22094 |
+ "allowEscalation": { |
|
22095 |
+ "type": "boolean", |
|
22096 |
+ "description": "AllowEscalation indicates whether you can request roles and their escalating resources" |
|
22040 | 22097 |
} |
22041 | 22098 |
} |
22042 | 22099 |
}, |
... | ... |
@@ -14,6 +14,7 @@ import ( |
14 | 14 |
"github.com/golang/glog" |
15 | 15 |
"github.com/openshift/origin/pkg/auth/authenticator" |
16 | 16 |
"github.com/openshift/origin/pkg/auth/server/csrf" |
17 |
+ scopeauthorizer "github.com/openshift/origin/pkg/authorization/authorizer/scope" |
|
17 | 18 |
oapi "github.com/openshift/origin/pkg/oauth/api" |
18 | 19 |
"github.com/openshift/origin/pkg/oauth/registry/oauthclient" |
19 | 20 |
"github.com/openshift/origin/pkg/oauth/registry/oauthclientauthorization" |
... | ... |
@@ -118,6 +119,12 @@ func (l *Grant) handleForm(user user.Info, w http.ResponseWriter, req *http.Requ |
118 | 118 |
return |
119 | 119 |
} |
120 | 120 |
|
121 |
+ if err := scopeauthorizer.ValidateScopeRestrictions(client, scope.Split(scopes)...); err != nil { |
|
122 |
+ failure := fmt.Sprintf("%v requested illegal scopes (%v): %v", client.Name, scopes, err) |
|
123 |
+ l.failed(failure, w, req) |
|
124 |
+ return |
|
125 |
+ } |
|
126 |
+ |
|
121 | 127 |
uri, err := getBaseURL(req) |
122 | 128 |
if err != nil { |
123 | 129 |
glog.Errorf("Unable to generate base URL: %v", err) |
... | ... |
@@ -185,6 +192,11 @@ func (l *Grant) handleGrant(user user.Info, w http.ResponseWriter, req *http.Req |
185 | 185 |
l.failed("Could not find client for client_id", w, req) |
186 | 186 |
return |
187 | 187 |
} |
188 |
+ if err := scopeauthorizer.ValidateScopeRestrictions(client, scope.Split(scopes)...); err != nil { |
|
189 |
+ failure := fmt.Sprintf("%v requested illegal scopes (%v): %v", client.Name, scopes, err) |
|
190 |
+ l.failed(failure, w, req) |
|
191 |
+ return |
|
192 |
+ } |
|
188 | 193 |
|
189 | 194 |
clientAuthID := l.authregistry.ClientAuthorizationName(user.GetName(), client.Name) |
190 | 195 |
|
... | ... |
@@ -36,9 +36,13 @@ func badAuth(err error) *testAuth { |
36 | 36 |
return &testAuth{Success: false, User: nil, Err: err} |
37 | 37 |
} |
38 | 38 |
|
39 |
-func goodClientRegistry(clientID string, redirectURIs []string) *test.ClientRegistry { |
|
40 |
- client := &oapi.OAuthClient{ObjectMeta: kapi.ObjectMeta{Name: clientID}, Secret: "mysecret", RedirectURIs: redirectURIs} |
|
39 |
+func goodClientRegistry(clientID string, redirectURIs []string, literalScopes []string, unrestrictedScopes bool) *test.ClientRegistry { |
|
40 |
+ client := &oapi.OAuthClient{ObjectMeta: kapi.ObjectMeta{Name: clientID}, Secret: "mysecret", RedirectURIs: redirectURIs, AllowAnyScope: unrestrictedScopes} |
|
41 | 41 |
client.Name = clientID |
42 |
+ if len(literalScopes) > 0 { |
|
43 |
+ client.ScopeRestrictions = []oapi.ScopeRestriction{{ExactValues: literalScopes}} |
|
44 |
+ } |
|
45 |
+ |
|
42 | 46 |
return &test.ClientRegistry{Client: client} |
43 | 47 |
} |
44 | 48 |
func badClientRegistry(err error) *test.ClientRegistry { |
... | ... |
@@ -79,7 +83,7 @@ func TestGrant(t *testing.T) { |
79 | 79 |
"display form": { |
80 | 80 |
CSRF: &csrf.FakeCSRF{Token: "test"}, |
81 | 81 |
Auth: goodAuth("username"), |
82 |
- ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), |
|
82 |
+ ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}, []string{"myscope1", "myscope2"}, false), |
|
83 | 83 |
AuthRegistry: emptyAuthRegistry(), |
84 | 84 |
Path: "/grant?client_id=myclient&scopes=myscope1%20myscope2&redirect_uri=/myredirect&then=/authorize", |
85 | 85 |
|
... | ... |
@@ -133,7 +137,7 @@ func TestGrant(t *testing.T) { |
133 | 133 |
"error when POST fails CSRF": { |
134 | 134 |
CSRF: &csrf.FakeCSRF{Token: "test"}, |
135 | 135 |
Auth: goodAuth("username"), |
136 |
- ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), |
|
136 |
+ ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}, []string{"myscope1", "myscope2"}, false), |
|
137 | 137 |
AuthRegistry: emptyAuthRegistry(), |
138 | 138 |
Path: "/grant", |
139 | 139 |
PostValues: url.Values{ |
... | ... |
@@ -181,7 +185,7 @@ func TestGrant(t *testing.T) { |
181 | 181 |
"successful create grant with redirect": { |
182 | 182 |
CSRF: &csrf.FakeCSRF{Token: "test"}, |
183 | 183 |
Auth: goodAuth("username"), |
184 |
- ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), |
|
184 |
+ ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}, []string{"myscope1", "myscope2"}, false), |
|
185 | 185 |
AuthRegistry: emptyAuthRegistry(), |
186 | 186 |
Path: "/grant", |
187 | 187 |
PostValues: url.Values{ |
... | ... |
@@ -201,7 +205,7 @@ func TestGrant(t *testing.T) { |
201 | 201 |
"successful create grant without redirect": { |
202 | 202 |
CSRF: &csrf.FakeCSRF{Token: "test"}, |
203 | 203 |
Auth: goodAuth("username"), |
204 |
- ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), |
|
204 |
+ ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}, []string{"myscope1", "myscope2"}, false), |
|
205 | 205 |
AuthRegistry: emptyAuthRegistry(), |
206 | 206 |
Path: "/grant", |
207 | 207 |
PostValues: url.Values{ |
... | ... |
@@ -223,7 +227,7 @@ func TestGrant(t *testing.T) { |
223 | 223 |
"successful update grant with identical scopes": { |
224 | 224 |
CSRF: &csrf.FakeCSRF{Token: "test"}, |
225 | 225 |
Auth: goodAuth("username"), |
226 |
- ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), |
|
226 |
+ ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}, []string{"myscope1", "myscope2"}, false), |
|
227 | 227 |
AuthRegistry: existingAuthRegistry([]string{"myscope2", "myscope1"}), |
228 | 228 |
Path: "/grant", |
229 | 229 |
PostValues: url.Values{ |
... | ... |
@@ -243,7 +247,7 @@ func TestGrant(t *testing.T) { |
243 | 243 |
"successful update grant with additional scopes": { |
244 | 244 |
CSRF: &csrf.FakeCSRF{Token: "test"}, |
245 | 245 |
Auth: goodAuth("username"), |
246 |
- ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), |
|
246 |
+ ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}, []string{"newscope1", "existingscope1", "existingscope2"}, false), |
|
247 | 247 |
AuthRegistry: existingAuthRegistry([]string{"existingscope2", "existingscope1"}), |
248 | 248 |
Path: "/grant", |
249 | 249 |
PostValues: url.Values{ |
... | ... |
@@ -263,7 +267,7 @@ func TestGrant(t *testing.T) { |
263 | 263 |
"successful reject grant": { |
264 | 264 |
CSRF: &csrf.FakeCSRF{Token: "test"}, |
265 | 265 |
Auth: goodAuth("username"), |
266 |
- ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}), |
|
266 |
+ ClientRegistry: goodClientRegistry("myclient", []string{"myredirect"}, []string{"myscope1", "myscope2"}, false), |
|
267 | 267 |
AuthRegistry: existingAuthRegistry([]string{"existingscope2", "existingscope1"}), |
268 | 268 |
Path: "/grant", |
269 | 269 |
PostValues: url.Values{ |
... | ... |
@@ -12,6 +12,7 @@ import ( |
12 | 12 |
|
13 | 13 |
authorizationapi "github.com/openshift/origin/pkg/authorization/api" |
14 | 14 |
"github.com/openshift/origin/pkg/authorization/rulevalidation" |
15 |
+ oauthapi "github.com/openshift/origin/pkg/oauth/api" |
|
15 | 16 |
userapi "github.com/openshift/origin/pkg/user/api" |
16 | 17 |
) |
17 | 18 |
|
... | ... |
@@ -135,6 +136,8 @@ var escalatingScopeResources = []unversioned.GroupResource{ |
135 | 135 |
// role:<clusterrole name>:<namespace to allow the cluster role, * means all> |
136 | 136 |
type clusterRoleEvaluator struct{} |
137 | 137 |
|
138 |
+var clusterRoleEvaluatorInstance = clusterRoleEvaluator{} |
|
139 |
+ |
|
138 | 140 |
func (clusterRoleEvaluator) Handles(scope string) bool { |
139 | 141 |
return strings.HasPrefix(scope, ClusterRoleIndicator) |
140 | 142 |
} |
... | ... |
@@ -267,3 +270,102 @@ func getAPIGroupSet(rule authorizationapi.PolicyRule) sets.String { |
267 | 267 |
|
268 | 268 |
return apiGroups |
269 | 269 |
} |
270 |
+ |
|
271 |
+func ValidateScopeRestrictions(client *oauthapi.OAuthClient, scopes ...string) error { |
|
272 |
+ if client.AllowAnyScope { |
|
273 |
+ return nil |
|
274 |
+ } |
|
275 |
+ if len(scopes) == 0 { |
|
276 |
+ return fmt.Errorf("%v may not request unscoped tokens", client.Name) |
|
277 |
+ } |
|
278 |
+ |
|
279 |
+ errs := []error{} |
|
280 |
+ for _, scope := range scopes { |
|
281 |
+ if err := validateScopeRestrictions(client, scope); err != nil { |
|
282 |
+ errs = append(errs, err) |
|
283 |
+ } |
|
284 |
+ } |
|
285 |
+ |
|
286 |
+ return kutilerrors.NewAggregate(errs) |
|
287 |
+} |
|
288 |
+ |
|
289 |
+func validateScopeRestrictions(client *oauthapi.OAuthClient, scope string) error { |
|
290 |
+ errs := []error{} |
|
291 |
+ |
|
292 |
+ if client.AllowAnyScope { |
|
293 |
+ return nil |
|
294 |
+ } |
|
295 |
+ |
|
296 |
+ for _, restriction := range client.ScopeRestrictions { |
|
297 |
+ if len(restriction.ExactValues) > 0 { |
|
298 |
+ if err := ValidateLiteralScopeRestrictions(scope, restriction.ExactValues); err != nil { |
|
299 |
+ errs = append(errs, err) |
|
300 |
+ continue |
|
301 |
+ } |
|
302 |
+ return nil |
|
303 |
+ } |
|
304 |
+ |
|
305 |
+ if restriction.ClusterRole != nil { |
|
306 |
+ if !clusterRoleEvaluatorInstance.Handles(scope) { |
|
307 |
+ continue |
|
308 |
+ } |
|
309 |
+ if err := ValidateClusterRoleScopeRestrictions(scope, *restriction.ClusterRole); err != nil { |
|
310 |
+ errs = append(errs, err) |
|
311 |
+ continue |
|
312 |
+ } |
|
313 |
+ return nil |
|
314 |
+ } |
|
315 |
+ } |
|
316 |
+ |
|
317 |
+ // if we got here, then nothing matched. If we already have errors, do nothing, otherwise add one to make it report failed. |
|
318 |
+ if len(errs) == 0 { |
|
319 |
+ errs = append(errs, fmt.Errorf("%v did not match any scope restriction", scope)) |
|
320 |
+ } |
|
321 |
+ |
|
322 |
+ return kutilerrors.NewAggregate(errs) |
|
323 |
+} |
|
324 |
+ |
|
325 |
+func ValidateLiteralScopeRestrictions(scope string, literals []string) error { |
|
326 |
+ for _, literal := range literals { |
|
327 |
+ if literal == scope { |
|
328 |
+ return nil |
|
329 |
+ } |
|
330 |
+ } |
|
331 |
+ |
|
332 |
+ return fmt.Errorf("%v not found in %v", scope, literals) |
|
333 |
+} |
|
334 |
+ |
|
335 |
+func ValidateClusterRoleScopeRestrictions(scope string, restriction oauthapi.ClusterRoleScopeRestriction) error { |
|
336 |
+ role, namespace, escalating, err := clusterRoleEvaluatorInstance.parseScope(scope) |
|
337 |
+ if err != nil { |
|
338 |
+ return err |
|
339 |
+ } |
|
340 |
+ |
|
341 |
+ foundName := false |
|
342 |
+ for _, restrictedRoleName := range restriction.RoleNames { |
|
343 |
+ if restrictedRoleName == "*" || restrictedRoleName == role { |
|
344 |
+ foundName = true |
|
345 |
+ break |
|
346 |
+ } |
|
347 |
+ } |
|
348 |
+ if !foundName { |
|
349 |
+ return fmt.Errorf("%v does not use an approved name", scope) |
|
350 |
+ } |
|
351 |
+ |
|
352 |
+ foundNamespace := false |
|
353 |
+ for _, restrictedNamespace := range restriction.Namespaces { |
|
354 |
+ if restrictedNamespace == "*" || restrictedNamespace == namespace { |
|
355 |
+ foundNamespace = true |
|
356 |
+ break |
|
357 |
+ } |
|
358 |
+ } |
|
359 |
+ if !foundNamespace { |
|
360 |
+ return fmt.Errorf("%v does not use an approved namespace", scope) |
|
361 |
+ } |
|
362 |
+ |
|
363 |
+ if escalating && !restriction.AllowEscalation { |
|
364 |
+ return fmt.Errorf("%v is not allowed to escalate", scope) |
|
365 |
+ } |
|
366 |
+ |
|
367 |
+ return nil |
|
368 |
+} |
270 | 369 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,158 @@ |
0 |
+package scope |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "strings" |
|
4 |
+ "testing" |
|
5 |
+ |
|
6 |
+ oauthapi "github.com/openshift/origin/pkg/oauth/api" |
|
7 |
+) |
|
8 |
+ |
|
9 |
+func TestValidateScopeRestrictions(t *testing.T) { |
|
10 |
+ testCases := []struct { |
|
11 |
+ name string |
|
12 |
+ scopes []string |
|
13 |
+ client *oauthapi.OAuthClient |
|
14 |
+ |
|
15 |
+ expectedErrors []string |
|
16 |
+ }{ |
|
17 |
+ { |
|
18 |
+ name: "unrestricted allows any", |
|
19 |
+ scopes: []string{"one"}, |
|
20 |
+ client: &oauthapi.OAuthClient{AllowAnyScope: true}, |
|
21 |
+ }, |
|
22 |
+ { |
|
23 |
+ name: "unrestricted allows empty", |
|
24 |
+ scopes: []string{""}, |
|
25 |
+ client: &oauthapi.OAuthClient{AllowAnyScope: true}, |
|
26 |
+ }, |
|
27 |
+ { |
|
28 |
+ name: "unrestricted allows none", |
|
29 |
+ scopes: []string{}, |
|
30 |
+ client: &oauthapi.OAuthClient{AllowAnyScope: true}, |
|
31 |
+ }, |
|
32 |
+ { |
|
33 |
+ name: "no restrictions denies any", |
|
34 |
+ scopes: []string{"one"}, |
|
35 |
+ client: &oauthapi.OAuthClient{}, |
|
36 |
+ expectedErrors: []string{`one did not match any scope restriction`}, |
|
37 |
+ }, |
|
38 |
+ { |
|
39 |
+ name: "no restrictions denies empty", |
|
40 |
+ scopes: []string{""}, |
|
41 |
+ client: &oauthapi.OAuthClient{}, |
|
42 |
+ expectedErrors: []string{`did not match any scope restriction`}, |
|
43 |
+ }, |
|
44 |
+ { |
|
45 |
+ name: "no restrictions denies none", |
|
46 |
+ scopes: []string{}, |
|
47 |
+ client: &oauthapi.OAuthClient{}, |
|
48 |
+ expectedErrors: []string{`may not request unscoped tokens`}, |
|
49 |
+ }, |
|
50 |
+ { |
|
51 |
+ name: "simple literal", |
|
52 |
+ scopes: []string{"one"}, |
|
53 |
+ client: &oauthapi.OAuthClient{ |
|
54 |
+ ScopeRestrictions: []oauthapi.ScopeRestriction{{ExactValues: []string{"two", "one"}}}, |
|
55 |
+ }, |
|
56 |
+ }, |
|
57 |
+ { |
|
58 |
+ name: "simple must match", |
|
59 |
+ scopes: []string{"missing"}, |
|
60 |
+ client: &oauthapi.OAuthClient{ |
|
61 |
+ ScopeRestrictions: []oauthapi.ScopeRestriction{{ExactValues: []string{"two", "one"}}}, |
|
62 |
+ }, |
|
63 |
+ expectedErrors: []string{`missing not found in [two one]`}, |
|
64 |
+ }, |
|
65 |
+ { |
|
66 |
+ name: "cluster role name must match", |
|
67 |
+ scopes: []string{ClusterRoleIndicator + "three:alfa"}, |
|
68 |
+ client: &oauthapi.OAuthClient{ |
|
69 |
+ ScopeRestrictions: []oauthapi.ScopeRestriction{{ClusterRole: &oauthapi.ClusterRoleScopeRestriction{ |
|
70 |
+ RoleNames: []string{"one", "two"}, |
|
71 |
+ Namespaces: []string{"alfa", "bravo"}, |
|
72 |
+ AllowEscalation: false, |
|
73 |
+ }}}, |
|
74 |
+ }, |
|
75 |
+ expectedErrors: []string{`role:three:alfa does not use an approved name`}, |
|
76 |
+ }, |
|
77 |
+ { |
|
78 |
+ name: "cluster role namespace must match", |
|
79 |
+ scopes: []string{ClusterRoleIndicator + "two:charlie"}, |
|
80 |
+ client: &oauthapi.OAuthClient{ |
|
81 |
+ ScopeRestrictions: []oauthapi.ScopeRestriction{{ClusterRole: &oauthapi.ClusterRoleScopeRestriction{ |
|
82 |
+ RoleNames: []string{"one", "two"}, |
|
83 |
+ Namespaces: []string{"alfa", "bravo"}, |
|
84 |
+ AllowEscalation: false, |
|
85 |
+ }}}, |
|
86 |
+ }, |
|
87 |
+ expectedErrors: []string{`role:two:charlie does not use an approved namespace`}, |
|
88 |
+ }, |
|
89 |
+ { |
|
90 |
+ name: "cluster role escalation must match", |
|
91 |
+ scopes: []string{ClusterRoleIndicator + "two:bravo:!"}, |
|
92 |
+ client: &oauthapi.OAuthClient{ |
|
93 |
+ ScopeRestrictions: []oauthapi.ScopeRestriction{{ClusterRole: &oauthapi.ClusterRoleScopeRestriction{ |
|
94 |
+ RoleNames: []string{"one", "two"}, |
|
95 |
+ Namespaces: []string{"alfa", "bravo"}, |
|
96 |
+ AllowEscalation: false, |
|
97 |
+ }}}, |
|
98 |
+ }, |
|
99 |
+ expectedErrors: []string{`role:two:bravo:! is not allowed to escalate`}, |
|
100 |
+ }, |
|
101 |
+ { |
|
102 |
+ name: "cluster role matches", |
|
103 |
+ scopes: []string{ClusterRoleIndicator + "two:bravo:!"}, |
|
104 |
+ client: &oauthapi.OAuthClient{ |
|
105 |
+ ScopeRestrictions: []oauthapi.ScopeRestriction{{ClusterRole: &oauthapi.ClusterRoleScopeRestriction{ |
|
106 |
+ RoleNames: []string{"one", "two"}, |
|
107 |
+ Namespaces: []string{"alfa", "bravo"}, |
|
108 |
+ AllowEscalation: true, |
|
109 |
+ }}}, |
|
110 |
+ }, |
|
111 |
+ }, |
|
112 |
+ { |
|
113 |
+ name: "cluster role matches 2", |
|
114 |
+ scopes: []string{ClusterRoleIndicator + "two:bravo"}, |
|
115 |
+ client: &oauthapi.OAuthClient{ |
|
116 |
+ ScopeRestrictions: []oauthapi.ScopeRestriction{{ClusterRole: &oauthapi.ClusterRoleScopeRestriction{ |
|
117 |
+ RoleNames: []string{"one", "two"}, |
|
118 |
+ Namespaces: []string{"alfa", "bravo"}, |
|
119 |
+ AllowEscalation: false, |
|
120 |
+ }}}, |
|
121 |
+ }, |
|
122 |
+ }, |
|
123 |
+ { |
|
124 |
+ name: "cluster role star matches", |
|
125 |
+ scopes: []string{ClusterRoleIndicator + "two:bravo"}, |
|
126 |
+ client: &oauthapi.OAuthClient{ |
|
127 |
+ ScopeRestrictions: []oauthapi.ScopeRestriction{{ClusterRole: &oauthapi.ClusterRoleScopeRestriction{ |
|
128 |
+ RoleNames: []string{"one", "two", "*"}, |
|
129 |
+ Namespaces: []string{"alfa", "bravo", "*"}, |
|
130 |
+ AllowEscalation: true, |
|
131 |
+ }}}, |
|
132 |
+ }, |
|
133 |
+ }, |
|
134 |
+ } |
|
135 |
+ |
|
136 |
+ for _, tc := range testCases { |
|
137 |
+ err := ValidateScopeRestrictions(tc.client, tc.scopes...) |
|
138 |
+ if err != nil && len(tc.expectedErrors) == 0 { |
|
139 |
+ t.Errorf("%s: unexpected error: %v", tc.name, err) |
|
140 |
+ continue |
|
141 |
+ } |
|
142 |
+ if err == nil && len(tc.expectedErrors) > 0 { |
|
143 |
+ t.Errorf("%s: missing error: %v", tc.name, tc.expectedErrors) |
|
144 |
+ continue |
|
145 |
+ } |
|
146 |
+ if err == nil && len(tc.expectedErrors) == 0 { |
|
147 |
+ continue |
|
148 |
+ } |
|
149 |
+ |
|
150 |
+ for _, expectedErr := range tc.expectedErrors { |
|
151 |
+ if !strings.Contains(err.Error(), expectedErr) { |
|
152 |
+ t.Errorf("%s: error %v missing %v", tc.name, err, expectedErr) |
|
153 |
+ } |
|
154 |
+ } |
|
155 |
+ } |
|
156 |
+ |
|
157 |
+} |
... | ... |
@@ -83,13 +83,14 @@ const ( |
83 | 83 |
func (c *AuthConfig) InstallAPI(container *restful.Container) ([]string, error) { |
84 | 84 |
mux := c.getMux(container) |
85 | 85 |
|
86 |
- accessTokenStorage := accesstokenetcd.NewREST(c.EtcdHelper, c.EtcdBackends...) |
|
87 |
- accessTokenRegistry := accesstokenregistry.NewRegistry(accessTokenStorage) |
|
88 |
- authorizeTokenStorage := authorizetokenetcd.NewREST(c.EtcdHelper, c.EtcdBackends...) |
|
89 |
- authorizeTokenRegistry := authorizetokenregistry.NewRegistry(authorizeTokenStorage) |
|
90 | 86 |
clientStorage := clientetcd.NewREST(c.EtcdHelper) |
91 | 87 |
clientRegistry := clientregistry.NewRegistry(clientStorage) |
92 |
- clientAuthStorage := clientauthetcd.NewREST(c.EtcdHelper) |
|
88 |
+ |
|
89 |
+ accessTokenStorage := accesstokenetcd.NewREST(c.EtcdHelper, clientRegistry, c.EtcdBackends...) |
|
90 |
+ accessTokenRegistry := accesstokenregistry.NewRegistry(accessTokenStorage) |
|
91 |
+ authorizeTokenStorage := authorizetokenetcd.NewREST(c.EtcdHelper, clientRegistry, c.EtcdBackends...) |
|
92 |
+ authorizeTokenRegistry := authorizetokenregistry.NewRegistry(authorizeTokenStorage) |
|
93 |
+ clientAuthStorage := clientauthetcd.NewREST(c.EtcdHelper, clientRegistry) |
|
93 | 94 |
clientAuthRegistry := clientauthregistry.NewRegistry(clientAuthStorage) |
94 | 95 |
|
95 | 96 |
errorPageHandler, err := c.getErrorHandler() |
... | ... |
@@ -242,6 +243,8 @@ func ensureOAuthClient(client oauthapi.OAuthClient, clientRegistry clientregistr |
242 | 242 |
if len(existing.Secret) == 0 { |
243 | 243 |
existing.Secret = client.Secret |
244 | 244 |
} |
245 |
+ // Ensure the correct scope setting |
|
246 |
+ existing.AllowAnyScope = client.AllowAnyScope |
|
245 | 247 |
|
246 | 248 |
// Preserve redirects for clients other than the CLI client |
247 | 249 |
// The CLI client doesn't care about the redirect URL, just the token or error fragment |
... | ... |
@@ -270,6 +273,7 @@ func CreateOrUpdateDefaultOAuthClients(masterPublicAddr string, assetPublicAddre |
270 | 270 |
Secret: uuid.New(), |
271 | 271 |
RespondWithChallenges: false, |
272 | 272 |
RedirectURIs: assetPublicAddresses, |
273 |
+ AllowAnyScope: true, |
|
273 | 274 |
} |
274 | 275 |
if err := ensureOAuthClient(webConsoleClient, clientRegistry, true); err != nil { |
275 | 276 |
return err |
... | ... |
@@ -282,6 +286,7 @@ func CreateOrUpdateDefaultOAuthClients(masterPublicAddr string, assetPublicAddre |
282 | 282 |
Secret: uuid.New(), |
283 | 283 |
RespondWithChallenges: false, |
284 | 284 |
RedirectURIs: []string{masterPublicAddr + path.Join(OpenShiftOAuthAPIPrefix, tokenrequest.DisplayTokenEndpoint)}, |
285 |
+ AllowAnyScope: true, |
|
285 | 286 |
} |
286 | 287 |
if err := ensureOAuthClient(browserClient, clientRegistry, true); err != nil { |
287 | 288 |
return err |
... | ... |
@@ -294,6 +299,7 @@ func CreateOrUpdateDefaultOAuthClients(masterPublicAddr string, assetPublicAddre |
294 | 294 |
Secret: uuid.New(), |
295 | 295 |
RespondWithChallenges: true, |
296 | 296 |
RedirectURIs: []string{masterPublicAddr + path.Join(OpenShiftOAuthAPIPrefix, tokenrequest.ImplicitTokenEndpoint)}, |
297 |
+ AllowAnyScope: true, |
|
297 | 298 |
} |
298 | 299 |
if err := ensureOAuthClient(cliClient, clientRegistry, false); err != nil { |
299 | 300 |
return err |
... | ... |
@@ -64,6 +64,7 @@ import ( |
64 | 64 |
"github.com/openshift/origin/pkg/image/registry/imagestreamtag" |
65 | 65 |
accesstokenetcd "github.com/openshift/origin/pkg/oauth/registry/oauthaccesstoken/etcd" |
66 | 66 |
authorizetokenetcd "github.com/openshift/origin/pkg/oauth/registry/oauthauthorizetoken/etcd" |
67 |
+ clientregistry "github.com/openshift/origin/pkg/oauth/registry/oauthclient" |
|
67 | 68 |
clientetcd "github.com/openshift/origin/pkg/oauth/registry/oauthclient/etcd" |
68 | 69 |
clientauthetcd "github.com/openshift/origin/pkg/oauth/registry/oauthclientauthorization/etcd" |
69 | 70 |
projectproxy "github.com/openshift/origin/pkg/project/registry/project/proxy" |
... | ... |
@@ -490,6 +491,9 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage { |
490 | 490 |
}, |
491 | 491 |
) |
492 | 492 |
|
493 |
+ clientStorage := clientetcd.NewREST(c.EtcdHelper) |
|
494 |
+ clientRegistry := clientregistry.NewRegistry(clientStorage) |
|
495 |
+ |
|
493 | 496 |
storage := map[string]rest.Storage{ |
494 | 497 |
"images": imageStorage, |
495 | 498 |
"imageStreams/secrets": imageStreamSecretsStorage, |
... | ... |
@@ -525,10 +529,10 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage { |
525 | 525 |
"identities": identityStorage, |
526 | 526 |
"userIdentityMappings": userIdentityMappingStorage, |
527 | 527 |
|
528 |
- "oAuthAuthorizeTokens": authorizetokenetcd.NewREST(c.EtcdHelper), |
|
529 |
- "oAuthAccessTokens": accesstokenetcd.NewREST(c.EtcdHelper), |
|
530 |
- "oAuthClients": clientetcd.NewREST(c.EtcdHelper), |
|
531 |
- "oAuthClientAuthorizations": clientauthetcd.NewREST(c.EtcdHelper), |
|
528 |
+ "oAuthAuthorizeTokens": authorizetokenetcd.NewREST(c.EtcdHelper, clientRegistry), |
|
529 |
+ "oAuthAccessTokens": accesstokenetcd.NewREST(c.EtcdHelper, clientRegistry), |
|
530 |
+ "oAuthClients": clientStorage, |
|
531 |
+ "oAuthClientAuthorizations": clientauthetcd.NewREST(c.EtcdHelper, clientRegistry), |
|
532 | 532 |
|
533 | 533 |
"resourceAccessReviews": resourceAccessReviewStorage, |
534 | 534 |
"subjectAccessReviews": subjectAccessReviewStorage, |
... | ... |
@@ -384,7 +384,8 @@ func newAuthorizationAttributeBuilder(requestContextMapper kapi.RequestContextMa |
384 | 384 |
} |
385 | 385 |
|
386 | 386 |
func getEtcdTokenAuthenticator(etcdHelper storage.Interface, groupMapper identitymapper.UserToGroupMapper) authenticator.Token { |
387 |
- accessTokenStorage := accesstokenetcd.NewREST(etcdHelper) |
|
387 |
+ // this never does a create for access tokens, so we don't need to be able to validate scopes against the client |
|
388 |
+ accessTokenStorage := accesstokenetcd.NewREST(etcdHelper, nil) |
|
388 | 389 |
accessTokenRegistry := accesstokenregistry.NewRegistry(accessTokenStorage) |
389 | 390 |
|
390 | 391 |
userStorage := useretcd.NewREST(etcdHelper) |
... | ... |
@@ -12,6 +12,7 @@ import ( |
12 | 12 |
|
13 | 13 |
func init() { |
14 | 14 |
if err := api.Scheme.AddGeneratedDeepCopyFuncs( |
15 |
+ DeepCopy_api_ClusterRoleScopeRestriction, |
|
15 | 16 |
DeepCopy_api_OAuthAccessToken, |
16 | 17 |
DeepCopy_api_OAuthAccessTokenList, |
17 | 18 |
DeepCopy_api_OAuthAuthorizeToken, |
... | ... |
@@ -20,12 +21,32 @@ func init() { |
20 | 20 |
DeepCopy_api_OAuthClientAuthorization, |
21 | 21 |
DeepCopy_api_OAuthClientAuthorizationList, |
22 | 22 |
DeepCopy_api_OAuthClientList, |
23 |
+ DeepCopy_api_ScopeRestriction, |
|
23 | 24 |
); err != nil { |
24 | 25 |
// if one of the deep copy functions is malformed, detect it immediately. |
25 | 26 |
panic(err) |
26 | 27 |
} |
27 | 28 |
} |
28 | 29 |
|
30 |
+func DeepCopy_api_ClusterRoleScopeRestriction(in ClusterRoleScopeRestriction, out *ClusterRoleScopeRestriction, c *conversion.Cloner) error { |
|
31 |
+ if in.RoleNames != nil { |
|
32 |
+ in, out := in.RoleNames, &out.RoleNames |
|
33 |
+ *out = make([]string, len(in)) |
|
34 |
+ copy(*out, in) |
|
35 |
+ } else { |
|
36 |
+ out.RoleNames = nil |
|
37 |
+ } |
|
38 |
+ if in.Namespaces != nil { |
|
39 |
+ in, out := in.Namespaces, &out.Namespaces |
|
40 |
+ *out = make([]string, len(in)) |
|
41 |
+ copy(*out, in) |
|
42 |
+ } else { |
|
43 |
+ out.Namespaces = nil |
|
44 |
+ } |
|
45 |
+ out.AllowEscalation = in.AllowEscalation |
|
46 |
+ return nil |
|
47 |
+} |
|
48 |
+ |
|
29 | 49 |
func DeepCopy_api_OAuthAccessToken(in OAuthAccessToken, out *OAuthAccessToken, c *conversion.Cloner) error { |
30 | 50 |
if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { |
31 | 51 |
return err |
... | ... |
@@ -131,6 +152,18 @@ func DeepCopy_api_OAuthClient(in OAuthClient, out *OAuthClient, c *conversion.Cl |
131 | 131 |
} else { |
132 | 132 |
out.RedirectURIs = nil |
133 | 133 |
} |
134 |
+ if in.ScopeRestrictions != nil { |
|
135 |
+ in, out := in.ScopeRestrictions, &out.ScopeRestrictions |
|
136 |
+ *out = make([]ScopeRestriction, len(in)) |
|
137 |
+ for i := range in { |
|
138 |
+ if err := DeepCopy_api_ScopeRestriction(in[i], &(*out)[i], c); err != nil { |
|
139 |
+ return err |
|
140 |
+ } |
|
141 |
+ } |
|
142 |
+ } else { |
|
143 |
+ out.ScopeRestrictions = nil |
|
144 |
+ } |
|
145 |
+ out.AllowAnyScope = in.AllowAnyScope |
|
134 | 146 |
return nil |
135 | 147 |
} |
136 | 148 |
|
... | ... |
@@ -195,3 +228,23 @@ func DeepCopy_api_OAuthClientList(in OAuthClientList, out *OAuthClientList, c *c |
195 | 195 |
} |
196 | 196 |
return nil |
197 | 197 |
} |
198 |
+ |
|
199 |
+func DeepCopy_api_ScopeRestriction(in ScopeRestriction, out *ScopeRestriction, c *conversion.Cloner) error { |
|
200 |
+ if in.ExactValues != nil { |
|
201 |
+ in, out := in.ExactValues, &out.ExactValues |
|
202 |
+ *out = make([]string, len(in)) |
|
203 |
+ copy(*out, in) |
|
204 |
+ } else { |
|
205 |
+ out.ExactValues = nil |
|
206 |
+ } |
|
207 |
+ if in.ClusterRole != nil { |
|
208 |
+ in, out := in.ClusterRole, &out.ClusterRole |
|
209 |
+ *out = new(ClusterRoleScopeRestriction) |
|
210 |
+ if err := DeepCopy_api_ClusterRoleScopeRestriction(*in, *out, c); err != nil { |
|
211 |
+ return err |
|
212 |
+ } |
|
213 |
+ } else { |
|
214 |
+ out.ClusterRole = nil |
|
215 |
+ } |
|
216 |
+ return nil |
|
217 |
+} |
... | ... |
@@ -73,6 +73,34 @@ type OAuthClient struct { |
73 | 73 |
|
74 | 74 |
// RedirectURIs is the valid redirection URIs associated with a client |
75 | 75 |
RedirectURIs []string |
76 |
+ |
|
77 |
+ // ScopeRestrictions describes which scopes this client can request. Each requested scope |
|
78 |
+ // is checked against each restriction. If any restriction matches, then the scope is allowed. |
|
79 |
+ // If no restriction matches, then the scope is denied. |
|
80 |
+ ScopeRestrictions []ScopeRestriction |
|
81 |
+ |
|
82 |
+ // AllowAnyScope indicates that the client is allowed to request a token unconstrained by scopes. |
|
83 |
+ // If this is true, then ScopeRestrictions is ignored. |
|
84 |
+ AllowAnyScope bool |
|
85 |
+} |
|
86 |
+ |
|
87 |
+// ScopeRestriction describe one restriction on scopes. Exactly one option must be non-nil. |
|
88 |
+type ScopeRestriction struct { |
|
89 |
+ // ExactValues means the scope has to match a particular set of strings exactly |
|
90 |
+ ExactValues []string |
|
91 |
+ |
|
92 |
+ // ClusterRole describes a set of restrictions for cluster role scoping. |
|
93 |
+ ClusterRole *ClusterRoleScopeRestriction |
|
94 |
+} |
|
95 |
+ |
|
96 |
+// ClusterRoleScopeRestriction describes restrictions on cluster role scopes |
|
97 |
+type ClusterRoleScopeRestriction struct { |
|
98 |
+ // RoleNames is the list of cluster roles that can referenced. * means anything |
|
99 |
+ RoleNames []string |
|
100 |
+ // Namespaces is the list of namespaces that can be referenced. * means any of them (including *) |
|
101 |
+ Namespaces []string |
|
102 |
+ // AllowEscalation indicates whether you can request roles and their escalating resources |
|
103 |
+ AllowEscalation bool |
|
76 | 104 |
} |
77 | 105 |
|
78 | 106 |
type OAuthClientAuthorization struct { |
... | ... |
@@ -13,6 +13,8 @@ import ( |
13 | 13 |
|
14 | 14 |
func init() { |
15 | 15 |
if err := api.Scheme.AddGeneratedConversionFuncs( |
16 |
+ Convert_v1_ClusterRoleScopeRestriction_To_api_ClusterRoleScopeRestriction, |
|
17 |
+ Convert_api_ClusterRoleScopeRestriction_To_v1_ClusterRoleScopeRestriction, |
|
16 | 18 |
Convert_v1_OAuthAccessToken_To_api_OAuthAccessToken, |
17 | 19 |
Convert_api_OAuthAccessToken_To_v1_OAuthAccessToken, |
18 | 20 |
Convert_v1_OAuthAccessTokenList_To_api_OAuthAccessTokenList, |
... | ... |
@@ -29,12 +31,66 @@ func init() { |
29 | 29 |
Convert_api_OAuthClientAuthorizationList_To_v1_OAuthClientAuthorizationList, |
30 | 30 |
Convert_v1_OAuthClientList_To_api_OAuthClientList, |
31 | 31 |
Convert_api_OAuthClientList_To_v1_OAuthClientList, |
32 |
+ Convert_v1_ScopeRestriction_To_api_ScopeRestriction, |
|
33 |
+ Convert_api_ScopeRestriction_To_v1_ScopeRestriction, |
|
32 | 34 |
); err != nil { |
33 | 35 |
// if one of the conversion functions is malformed, detect it immediately. |
34 | 36 |
panic(err) |
35 | 37 |
} |
36 | 38 |
} |
37 | 39 |
|
40 |
+func autoConvert_v1_ClusterRoleScopeRestriction_To_api_ClusterRoleScopeRestriction(in *ClusterRoleScopeRestriction, out *oauth_api.ClusterRoleScopeRestriction, s conversion.Scope) error { |
|
41 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { |
|
42 |
+ defaulting.(func(*ClusterRoleScopeRestriction))(in) |
|
43 |
+ } |
|
44 |
+ if in.RoleNames != nil { |
|
45 |
+ in, out := &in.RoleNames, &out.RoleNames |
|
46 |
+ *out = make([]string, len(*in)) |
|
47 |
+ copy(*out, *in) |
|
48 |
+ } else { |
|
49 |
+ out.RoleNames = nil |
|
50 |
+ } |
|
51 |
+ if in.Namespaces != nil { |
|
52 |
+ in, out := &in.Namespaces, &out.Namespaces |
|
53 |
+ *out = make([]string, len(*in)) |
|
54 |
+ copy(*out, *in) |
|
55 |
+ } else { |
|
56 |
+ out.Namespaces = nil |
|
57 |
+ } |
|
58 |
+ out.AllowEscalation = in.AllowEscalation |
|
59 |
+ return nil |
|
60 |
+} |
|
61 |
+ |
|
62 |
+func Convert_v1_ClusterRoleScopeRestriction_To_api_ClusterRoleScopeRestriction(in *ClusterRoleScopeRestriction, out *oauth_api.ClusterRoleScopeRestriction, s conversion.Scope) error { |
|
63 |
+ return autoConvert_v1_ClusterRoleScopeRestriction_To_api_ClusterRoleScopeRestriction(in, out, s) |
|
64 |
+} |
|
65 |
+ |
|
66 |
+func autoConvert_api_ClusterRoleScopeRestriction_To_v1_ClusterRoleScopeRestriction(in *oauth_api.ClusterRoleScopeRestriction, out *ClusterRoleScopeRestriction, s conversion.Scope) error { |
|
67 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { |
|
68 |
+ defaulting.(func(*oauth_api.ClusterRoleScopeRestriction))(in) |
|
69 |
+ } |
|
70 |
+ if in.RoleNames != nil { |
|
71 |
+ in, out := &in.RoleNames, &out.RoleNames |
|
72 |
+ *out = make([]string, len(*in)) |
|
73 |
+ copy(*out, *in) |
|
74 |
+ } else { |
|
75 |
+ out.RoleNames = nil |
|
76 |
+ } |
|
77 |
+ if in.Namespaces != nil { |
|
78 |
+ in, out := &in.Namespaces, &out.Namespaces |
|
79 |
+ *out = make([]string, len(*in)) |
|
80 |
+ copy(*out, *in) |
|
81 |
+ } else { |
|
82 |
+ out.Namespaces = nil |
|
83 |
+ } |
|
84 |
+ out.AllowEscalation = in.AllowEscalation |
|
85 |
+ return nil |
|
86 |
+} |
|
87 |
+ |
|
88 |
+func Convert_api_ClusterRoleScopeRestriction_To_v1_ClusterRoleScopeRestriction(in *oauth_api.ClusterRoleScopeRestriction, out *ClusterRoleScopeRestriction, s conversion.Scope) error { |
|
89 |
+ return autoConvert_api_ClusterRoleScopeRestriction_To_v1_ClusterRoleScopeRestriction(in, out, s) |
|
90 |
+} |
|
91 |
+ |
|
38 | 92 |
func autoConvert_v1_OAuthAccessToken_To_api_OAuthAccessToken(in *OAuthAccessToken, out *oauth_api.OAuthAccessToken, s conversion.Scope) error { |
39 | 93 |
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { |
40 | 94 |
defaulting.(func(*OAuthAccessToken))(in) |
... | ... |
@@ -293,6 +349,18 @@ func autoConvert_v1_OAuthClient_To_api_OAuthClient(in *OAuthClient, out *oauth_a |
293 | 293 |
} else { |
294 | 294 |
out.RedirectURIs = nil |
295 | 295 |
} |
296 |
+ if in.ScopeRestrictions != nil { |
|
297 |
+ in, out := &in.ScopeRestrictions, &out.ScopeRestrictions |
|
298 |
+ *out = make([]oauth_api.ScopeRestriction, len(*in)) |
|
299 |
+ for i := range *in { |
|
300 |
+ if err := Convert_v1_ScopeRestriction_To_api_ScopeRestriction(&(*in)[i], &(*out)[i], s); err != nil { |
|
301 |
+ return err |
|
302 |
+ } |
|
303 |
+ } |
|
304 |
+ } else { |
|
305 |
+ out.ScopeRestrictions = nil |
|
306 |
+ } |
|
307 |
+ out.AllowAnyScope = in.AllowAnyScope |
|
296 | 308 |
return nil |
297 | 309 |
} |
298 | 310 |
|
... | ... |
@@ -320,6 +388,18 @@ func autoConvert_api_OAuthClient_To_v1_OAuthClient(in *oauth_api.OAuthClient, ou |
320 | 320 |
} else { |
321 | 321 |
out.RedirectURIs = nil |
322 | 322 |
} |
323 |
+ if in.ScopeRestrictions != nil { |
|
324 |
+ in, out := &in.ScopeRestrictions, &out.ScopeRestrictions |
|
325 |
+ *out = make([]ScopeRestriction, len(*in)) |
|
326 |
+ for i := range *in { |
|
327 |
+ if err := Convert_api_ScopeRestriction_To_v1_ScopeRestriction(&(*in)[i], &(*out)[i], s); err != nil { |
|
328 |
+ return err |
|
329 |
+ } |
|
330 |
+ } |
|
331 |
+ } else { |
|
332 |
+ out.ScopeRestrictions = nil |
|
333 |
+ } |
|
334 |
+ out.AllowAnyScope = in.AllowAnyScope |
|
323 | 335 |
return nil |
324 | 336 |
} |
325 | 337 |
|
... | ... |
@@ -494,3 +574,57 @@ func autoConvert_api_OAuthClientList_To_v1_OAuthClientList(in *oauth_api.OAuthCl |
494 | 494 |
func Convert_api_OAuthClientList_To_v1_OAuthClientList(in *oauth_api.OAuthClientList, out *OAuthClientList, s conversion.Scope) error { |
495 | 495 |
return autoConvert_api_OAuthClientList_To_v1_OAuthClientList(in, out, s) |
496 | 496 |
} |
497 |
+ |
|
498 |
+func autoConvert_v1_ScopeRestriction_To_api_ScopeRestriction(in *ScopeRestriction, out *oauth_api.ScopeRestriction, s conversion.Scope) error { |
|
499 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { |
|
500 |
+ defaulting.(func(*ScopeRestriction))(in) |
|
501 |
+ } |
|
502 |
+ if in.ExactValues != nil { |
|
503 |
+ in, out := &in.ExactValues, &out.ExactValues |
|
504 |
+ *out = make([]string, len(*in)) |
|
505 |
+ copy(*out, *in) |
|
506 |
+ } else { |
|
507 |
+ out.ExactValues = nil |
|
508 |
+ } |
|
509 |
+ if in.ClusterRole != nil { |
|
510 |
+ in, out := &in.ClusterRole, &out.ClusterRole |
|
511 |
+ *out = new(oauth_api.ClusterRoleScopeRestriction) |
|
512 |
+ if err := Convert_v1_ClusterRoleScopeRestriction_To_api_ClusterRoleScopeRestriction(*in, *out, s); err != nil { |
|
513 |
+ return err |
|
514 |
+ } |
|
515 |
+ } else { |
|
516 |
+ out.ClusterRole = nil |
|
517 |
+ } |
|
518 |
+ return nil |
|
519 |
+} |
|
520 |
+ |
|
521 |
+func Convert_v1_ScopeRestriction_To_api_ScopeRestriction(in *ScopeRestriction, out *oauth_api.ScopeRestriction, s conversion.Scope) error { |
|
522 |
+ return autoConvert_v1_ScopeRestriction_To_api_ScopeRestriction(in, out, s) |
|
523 |
+} |
|
524 |
+ |
|
525 |
+func autoConvert_api_ScopeRestriction_To_v1_ScopeRestriction(in *oauth_api.ScopeRestriction, out *ScopeRestriction, s conversion.Scope) error { |
|
526 |
+ if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { |
|
527 |
+ defaulting.(func(*oauth_api.ScopeRestriction))(in) |
|
528 |
+ } |
|
529 |
+ if in.ExactValues != nil { |
|
530 |
+ in, out := &in.ExactValues, &out.ExactValues |
|
531 |
+ *out = make([]string, len(*in)) |
|
532 |
+ copy(*out, *in) |
|
533 |
+ } else { |
|
534 |
+ out.ExactValues = nil |
|
535 |
+ } |
|
536 |
+ if in.ClusterRole != nil { |
|
537 |
+ in, out := &in.ClusterRole, &out.ClusterRole |
|
538 |
+ *out = new(ClusterRoleScopeRestriction) |
|
539 |
+ if err := Convert_api_ClusterRoleScopeRestriction_To_v1_ClusterRoleScopeRestriction(*in, *out, s); err != nil { |
|
540 |
+ return err |
|
541 |
+ } |
|
542 |
+ } else { |
|
543 |
+ out.ClusterRole = nil |
|
544 |
+ } |
|
545 |
+ return nil |
|
546 |
+} |
|
547 |
+ |
|
548 |
+func Convert_api_ScopeRestriction_To_v1_ScopeRestriction(in *oauth_api.ScopeRestriction, out *ScopeRestriction, s conversion.Scope) error { |
|
549 |
+ return autoConvert_api_ScopeRestriction_To_v1_ScopeRestriction(in, out, s) |
|
550 |
+} |
... | ... |
@@ -13,6 +13,7 @@ import ( |
13 | 13 |
|
14 | 14 |
func init() { |
15 | 15 |
if err := api.Scheme.AddGeneratedDeepCopyFuncs( |
16 |
+ DeepCopy_v1_ClusterRoleScopeRestriction, |
|
16 | 17 |
DeepCopy_v1_OAuthAccessToken, |
17 | 18 |
DeepCopy_v1_OAuthAccessTokenList, |
18 | 19 |
DeepCopy_v1_OAuthAuthorizeToken, |
... | ... |
@@ -21,12 +22,32 @@ func init() { |
21 | 21 |
DeepCopy_v1_OAuthClientAuthorization, |
22 | 22 |
DeepCopy_v1_OAuthClientAuthorizationList, |
23 | 23 |
DeepCopy_v1_OAuthClientList, |
24 |
+ DeepCopy_v1_ScopeRestriction, |
|
24 | 25 |
); err != nil { |
25 | 26 |
// if one of the deep copy functions is malformed, detect it immediately. |
26 | 27 |
panic(err) |
27 | 28 |
} |
28 | 29 |
} |
29 | 30 |
|
31 |
+func DeepCopy_v1_ClusterRoleScopeRestriction(in ClusterRoleScopeRestriction, out *ClusterRoleScopeRestriction, c *conversion.Cloner) error { |
|
32 |
+ if in.RoleNames != nil { |
|
33 |
+ in, out := in.RoleNames, &out.RoleNames |
|
34 |
+ *out = make([]string, len(in)) |
|
35 |
+ copy(*out, in) |
|
36 |
+ } else { |
|
37 |
+ out.RoleNames = nil |
|
38 |
+ } |
|
39 |
+ if in.Namespaces != nil { |
|
40 |
+ in, out := in.Namespaces, &out.Namespaces |
|
41 |
+ *out = make([]string, len(in)) |
|
42 |
+ copy(*out, in) |
|
43 |
+ } else { |
|
44 |
+ out.Namespaces = nil |
|
45 |
+ } |
|
46 |
+ out.AllowEscalation = in.AllowEscalation |
|
47 |
+ return nil |
|
48 |
+} |
|
49 |
+ |
|
30 | 50 |
func DeepCopy_v1_OAuthAccessToken(in OAuthAccessToken, out *OAuthAccessToken, c *conversion.Cloner) error { |
31 | 51 |
if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { |
32 | 52 |
return err |
... | ... |
@@ -132,6 +153,18 @@ func DeepCopy_v1_OAuthClient(in OAuthClient, out *OAuthClient, c *conversion.Clo |
132 | 132 |
} else { |
133 | 133 |
out.RedirectURIs = nil |
134 | 134 |
} |
135 |
+ if in.ScopeRestrictions != nil { |
|
136 |
+ in, out := in.ScopeRestrictions, &out.ScopeRestrictions |
|
137 |
+ *out = make([]ScopeRestriction, len(in)) |
|
138 |
+ for i := range in { |
|
139 |
+ if err := DeepCopy_v1_ScopeRestriction(in[i], &(*out)[i], c); err != nil { |
|
140 |
+ return err |
|
141 |
+ } |
|
142 |
+ } |
|
143 |
+ } else { |
|
144 |
+ out.ScopeRestrictions = nil |
|
145 |
+ } |
|
146 |
+ out.AllowAnyScope = in.AllowAnyScope |
|
135 | 147 |
return nil |
136 | 148 |
} |
137 | 149 |
|
... | ... |
@@ -196,3 +229,23 @@ func DeepCopy_v1_OAuthClientList(in OAuthClientList, out *OAuthClientList, c *co |
196 | 196 |
} |
197 | 197 |
return nil |
198 | 198 |
} |
199 |
+ |
|
200 |
+func DeepCopy_v1_ScopeRestriction(in ScopeRestriction, out *ScopeRestriction, c *conversion.Cloner) error { |
|
201 |
+ if in.ExactValues != nil { |
|
202 |
+ in, out := in.ExactValues, &out.ExactValues |
|
203 |
+ *out = make([]string, len(in)) |
|
204 |
+ copy(*out, in) |
|
205 |
+ } else { |
|
206 |
+ out.ExactValues = nil |
|
207 |
+ } |
|
208 |
+ if in.ClusterRole != nil { |
|
209 |
+ in, out := in.ClusterRole, &out.ClusterRole |
|
210 |
+ *out = new(ClusterRoleScopeRestriction) |
|
211 |
+ if err := DeepCopy_v1_ClusterRoleScopeRestriction(*in, *out, c); err != nil { |
|
212 |
+ return err |
|
213 |
+ } |
|
214 |
+ } else { |
|
215 |
+ out.ClusterRole = nil |
|
216 |
+ } |
|
217 |
+ return nil |
|
218 |
+} |
... | ... |
@@ -5,6 +5,17 @@ package v1 |
5 | 5 |
// by hack/update-generated-swagger-descriptions.sh and should be run after a full build of OpenShift. |
6 | 6 |
// ==== DO NOT EDIT THIS FILE MANUALLY ==== |
7 | 7 |
|
8 |
+var map_ClusterRoleScopeRestriction = map[string]string{ |
|
9 |
+ "": "ClusterRoleScopeRestriction describes restrictions on cluster role scopes", |
|
10 |
+ "roleNames": "RoleNames is the list of cluster roles that can referenced. * means anything", |
|
11 |
+ "namespaces": "Namespaces is the list of namespaces that can be referenced. * means any of them (including *)", |
|
12 |
+ "allowEscalation": "AllowEscalation indicates whether you can request roles and their escalating resources", |
|
13 |
+} |
|
14 |
+ |
|
15 |
+func (ClusterRoleScopeRestriction) SwaggerDoc() map[string]string { |
|
16 |
+ return map_ClusterRoleScopeRestriction |
|
17 |
+} |
|
18 |
+ |
|
8 | 19 |
var map_OAuthAccessToken = map[string]string{ |
9 | 20 |
"": "OAuthAccessToken describes an OAuth access token", |
10 | 21 |
"metadata": "Standard object's metadata.", |
... | ... |
@@ -64,6 +75,8 @@ var map_OAuthClient = map[string]string{ |
64 | 64 |
"secret": "Secret is the unique secret associated with a client", |
65 | 65 |
"respondWithChallenges": "RespondWithChallenges indicates whether the client wants authentication needed responses made in the form of challenges instead of redirects", |
66 | 66 |
"redirectURIs": "RedirectURIs is the valid redirection URIs associated with a client", |
67 |
+ "scopeRestrictions": "ScopeRestrictions describes which scopes this client can request. Each requested scope is checked against each restriction. If any restriction matches, then the scope is allowed. If no restriction matches, then the scope is denied.", |
|
68 |
+ "allowAnyScope": "AllowAnyScope indicates that the client is allowed to request a token unconstrained by scopes. If this is true, then ScopeRestrictions is ignored.", |
|
67 | 69 |
} |
68 | 70 |
|
69 | 71 |
func (OAuthClient) SwaggerDoc() map[string]string { |
... | ... |
@@ -102,3 +115,13 @@ var map_OAuthClientList = map[string]string{ |
102 | 102 |
func (OAuthClientList) SwaggerDoc() map[string]string { |
103 | 103 |
return map_OAuthClientList |
104 | 104 |
} |
105 |
+ |
|
106 |
+var map_ScopeRestriction = map[string]string{ |
|
107 |
+ "": "ScopeRestriction describe one restriction on scopes. Exactly one option must be non-nil.", |
|
108 |
+ "literals": "ExactValues means the scope has to match a particular set of strings exactly", |
|
109 |
+ "clusterRole": "ClusterRole describes a set of restrictions for cluster role scoping.", |
|
110 |
+} |
|
111 |
+ |
|
112 |
+func (ScopeRestriction) SwaggerDoc() map[string]string { |
|
113 |
+ return map_ScopeRestriction |
|
114 |
+} |
... | ... |
@@ -79,6 +79,34 @@ type OAuthClient struct { |
79 | 79 |
|
80 | 80 |
// RedirectURIs is the valid redirection URIs associated with a client |
81 | 81 |
RedirectURIs []string `json:"redirectURIs,omitempty"` |
82 |
+ |
|
83 |
+ // ScopeRestrictions describes which scopes this client can request. Each requested scope |
|
84 |
+ // is checked against each restriction. If any restriction matches, then the scope is allowed. |
|
85 |
+ // If no restriction matches, then the scope is denied. |
|
86 |
+ ScopeRestrictions []ScopeRestriction `json:"scopeRestrictions,omitempty"` |
|
87 |
+ |
|
88 |
+ // AllowAnyScope indicates that the client is allowed to request a token unconstrained by scopes. |
|
89 |
+ // If this is true, then ScopeRestrictions is ignored. |
|
90 |
+ AllowAnyScope bool `json:"allowAnyScope,omitempty"` |
|
91 |
+} |
|
92 |
+ |
|
93 |
+// ScopeRestriction describe one restriction on scopes. Exactly one option must be non-nil. |
|
94 |
+type ScopeRestriction struct { |
|
95 |
+ // ExactValues means the scope has to match a particular set of strings exactly |
|
96 |
+ ExactValues []string `json:"literals,omitempty"` |
|
97 |
+ |
|
98 |
+ // ClusterRole describes a set of restrictions for cluster role scoping. |
|
99 |
+ ClusterRole *ClusterRoleScopeRestriction `json:"clusterRole,omitempty"` |
|
100 |
+} |
|
101 |
+ |
|
102 |
+// ClusterRoleScopeRestriction describes restrictions on cluster role scopes |
|
103 |
+type ClusterRoleScopeRestriction struct { |
|
104 |
+ // RoleNames is the list of cluster roles that can referenced. * means anything |
|
105 |
+ RoleNames []string `json:"roleNames"` |
|
106 |
+ // Namespaces is the list of namespaces that can be referenced. * means any of them (including *) |
|
107 |
+ Namespaces []string `json:"namespaces"` |
|
108 |
+ // AllowEscalation indicates whether you can request roles and their escalating resources |
|
109 |
+ AllowEscalation bool `json:"allowEscalation"` |
|
82 | 110 |
} |
83 | 111 |
|
84 | 112 |
// OAuthClientAuthorization describes an authorization created by an OAuth client |
... | ... |
@@ -73,6 +73,34 @@ type OAuthClient struct { |
73 | 73 |
|
74 | 74 |
// RedirectURIs is the valid redirection URIs associated with a client |
75 | 75 |
RedirectURIs []string `json:"redirectURIs,omitempty"` |
76 |
+ |
|
77 |
+ // ScopeRestrictions describes which scopes this client can request. Each requested scope |
|
78 |
+ // is checked against each restriction. If any restriction matches, then the scope is allowed. |
|
79 |
+ // If no restriction matches, then the scope is denied. |
|
80 |
+ ScopeRestrictions []ScopeRestriction `json:"scopeRestrictions,omitempty"` |
|
81 |
+ |
|
82 |
+ // AllowAnyScope indicates that the client is allowed to request a token unconstrained by scopes. |
|
83 |
+ // If this is true, then ScopeRestrictions is ignored. |
|
84 |
+ AllowAnyScope bool `json:"allowAnyScope,omitempty"` |
|
85 |
+} |
|
86 |
+ |
|
87 |
+// ScopeRestriction describe one restriction on scopes. Exactly one option must be non-nil. |
|
88 |
+type ScopeRestriction struct { |
|
89 |
+ // ExactValues means the scope has to match a particular set of strings exactly |
|
90 |
+ ExactValues []string `json:"literals,omitempty"` |
|
91 |
+ |
|
92 |
+ // ClusterRole describes a set of restrictions for cluster role scoping. |
|
93 |
+ ClusterRole *ClusterRoleScopeRestriction `json:"clusterRole,omitempty"` |
|
94 |
+} |
|
95 |
+ |
|
96 |
+// ClusterRoleScopeRestriction describes restrictions on cluster role scopes |
|
97 |
+type ClusterRoleScopeRestriction struct { |
|
98 |
+ // RoleNames is the list of cluster roles that can referenced. * means anything |
|
99 |
+ RoleNames []string `json:"roleNames"` |
|
100 |
+ // Namespaces is the list of namespaces that can be referenced. * means any of them (including *) |
|
101 |
+ Namespaces []string `json:"namespaces"` |
|
102 |
+ // AllowEscalation indicates whether you can request roles and their escalating resources |
|
103 |
+ AllowEscalation bool `json:"allowEscalation"` |
|
76 | 104 |
} |
77 | 105 |
|
78 | 106 |
type OAuthClientAuthorization struct { |
... | ... |
@@ -90,6 +90,52 @@ func ValidateClient(client *api.OAuthClient) field.ErrorList { |
90 | 90 |
} |
91 | 91 |
} |
92 | 92 |
|
93 |
+ if client.AllowAnyScope && len(client.ScopeRestrictions) > 0 { |
|
94 |
+ allErrs = append(allErrs, field.Invalid(field.NewPath("scopeRestrictions"), client.ScopeRestrictions, "invalid when allowAnyScope is allowed")) |
|
95 |
+ } |
|
96 |
+ if !client.AllowAnyScope && len(client.ScopeRestrictions) == 0 { |
|
97 |
+ allErrs = append(allErrs, field.Required(field.NewPath("scopeRestrictions"), "required when allowAnyScope is false")) |
|
98 |
+ } |
|
99 |
+ |
|
100 |
+ for i, restriction := range client.ScopeRestrictions { |
|
101 |
+ allErrs = append(allErrs, ValidateScopeRestriction(restriction, field.NewPath("scopeRestrictions").Index(i))...) |
|
102 |
+ } |
|
103 |
+ |
|
104 |
+ return allErrs |
|
105 |
+} |
|
106 |
+ |
|
107 |
+func ValidateScopeRestriction(restriction api.ScopeRestriction, fldPath *field.Path) field.ErrorList { |
|
108 |
+ allErrs := field.ErrorList{} |
|
109 |
+ |
|
110 |
+ specifiers := 0 |
|
111 |
+ if len(restriction.ExactValues) > 0 { |
|
112 |
+ specifiers = specifiers + 1 |
|
113 |
+ } |
|
114 |
+ if restriction.ClusterRole != nil { |
|
115 |
+ specifiers = specifiers + 1 |
|
116 |
+ } |
|
117 |
+ if specifiers != 1 { |
|
118 |
+ allErrs = append(allErrs, field.Invalid(fldPath, restriction, "exactly one of literals, clusterRole is required")) |
|
119 |
+ return allErrs |
|
120 |
+ } |
|
121 |
+ |
|
122 |
+ switch { |
|
123 |
+ case len(restriction.ExactValues) > 0: |
|
124 |
+ for i, literal := range restriction.ExactValues { |
|
125 |
+ if len(literal) == 0 { |
|
126 |
+ allErrs = append(allErrs, field.Invalid(fldPath.Child("literals").Index(i), literal, "may not be empty")) |
|
127 |
+ } |
|
128 |
+ } |
|
129 |
+ |
|
130 |
+ case restriction.ClusterRole != nil: |
|
131 |
+ if len(restriction.ClusterRole.RoleNames) == 0 { |
|
132 |
+ allErrs = append(allErrs, field.Required(fldPath.Child("clusterRole", "roleNames"), "won't match anything")) |
|
133 |
+ } |
|
134 |
+ if len(restriction.ClusterRole.Namespaces) == 0 { |
|
135 |
+ allErrs = append(allErrs, field.Required(fldPath.Child("clusterRole", "namespaces"), "won't match anything")) |
|
136 |
+ } |
|
137 |
+ } |
|
138 |
+ |
|
93 | 139 |
return allErrs |
94 | 140 |
} |
95 | 141 |
|
... | ... |
@@ -173,7 +173,8 @@ func TestValidateClientAuthorization(t *testing.T) { |
173 | 173 |
|
174 | 174 |
func TestValidateClient(t *testing.T) { |
175 | 175 |
errs := ValidateClient(&oapi.OAuthClient{ |
176 |
- ObjectMeta: api.ObjectMeta{Name: "client-name"}, |
|
176 |
+ ObjectMeta: api.ObjectMeta{Name: "client-name"}, |
|
177 |
+ AllowAnyScope: true, |
|
177 | 178 |
}) |
178 | 179 |
if len(errs) != 0 { |
179 | 180 |
t.Errorf("expected success: %v", errs) |
... | ... |
@@ -185,15 +186,84 @@ func TestValidateClient(t *testing.T) { |
185 | 185 |
F string |
186 | 186 |
}{ |
187 | 187 |
"zero-length name": { |
188 |
- Client: oapi.OAuthClient{}, |
|
188 |
+ Client: oapi.OAuthClient{AllowAnyScope: true}, |
|
189 | 189 |
T: field.ErrorTypeRequired, |
190 | 190 |
F: "metadata.name", |
191 | 191 |
}, |
192 | 192 |
"disallowed namespace": { |
193 |
- Client: oapi.OAuthClient{ObjectMeta: api.ObjectMeta{Name: "name", Namespace: "foo"}}, |
|
193 |
+ Client: oapi.OAuthClient{ObjectMeta: api.ObjectMeta{Name: "name", Namespace: "foo"}, AllowAnyScope: true}, |
|
194 | 194 |
T: field.ErrorTypeForbidden, |
195 | 195 |
F: "metadata.namespace", |
196 | 196 |
}, |
197 |
+ "some scope note": { |
|
198 |
+ Client: oapi.OAuthClient{ |
|
199 |
+ ObjectMeta: api.ObjectMeta{Name: "client-name"}, |
|
200 |
+ }, |
|
201 |
+ T: field.ErrorTypeRequired, |
|
202 |
+ F: "scopeRestrictions", |
|
203 |
+ }, |
|
204 |
+ "not both scope notes": { |
|
205 |
+ Client: oapi.OAuthClient{ |
|
206 |
+ ObjectMeta: api.ObjectMeta{Name: "client-name"}, |
|
207 |
+ ScopeRestrictions: []oapi.ScopeRestriction{{ExactValues: []string{"a"}}}, |
|
208 |
+ AllowAnyScope: true, |
|
209 |
+ }, |
|
210 |
+ T: field.ErrorTypeInvalid, |
|
211 |
+ F: "scopeRestrictions", |
|
212 |
+ }, |
|
213 |
+ "literal must have value": { |
|
214 |
+ Client: oapi.OAuthClient{ |
|
215 |
+ ObjectMeta: api.ObjectMeta{Name: "client-name"}, |
|
216 |
+ ScopeRestrictions: []oapi.ScopeRestriction{{ExactValues: []string{""}}}, |
|
217 |
+ }, |
|
218 |
+ T: field.ErrorTypeInvalid, |
|
219 |
+ F: "scopeRestrictions[0].literals[0]", |
|
220 |
+ }, |
|
221 |
+ "must have some restriction": { |
|
222 |
+ Client: oapi.OAuthClient{ |
|
223 |
+ ObjectMeta: api.ObjectMeta{Name: "client-name"}, |
|
224 |
+ ScopeRestrictions: []oapi.ScopeRestriction{{}}, |
|
225 |
+ }, |
|
226 |
+ T: field.ErrorTypeInvalid, |
|
227 |
+ F: "scopeRestrictions[0]", |
|
228 |
+ }, |
|
229 |
+ "can't have both restrictions": { |
|
230 |
+ Client: oapi.OAuthClient{ |
|
231 |
+ ObjectMeta: api.ObjectMeta{Name: "client-name"}, |
|
232 |
+ ScopeRestrictions: []oapi.ScopeRestriction{ |
|
233 |
+ { |
|
234 |
+ ExactValues: []string{""}, |
|
235 |
+ ClusterRole: &oapi.ClusterRoleScopeRestriction{RoleNames: []string{"a"}, Namespaces: []string{"b"}}, |
|
236 |
+ }, |
|
237 |
+ }, |
|
238 |
+ }, |
|
239 |
+ T: field.ErrorTypeInvalid, |
|
240 |
+ F: "scopeRestrictions[0]", |
|
241 |
+ }, |
|
242 |
+ "must have role names": { |
|
243 |
+ Client: oapi.OAuthClient{ |
|
244 |
+ ObjectMeta: api.ObjectMeta{Name: "client-name"}, |
|
245 |
+ ScopeRestrictions: []oapi.ScopeRestriction{ |
|
246 |
+ { |
|
247 |
+ ClusterRole: &oapi.ClusterRoleScopeRestriction{Namespaces: []string{"b"}}, |
|
248 |
+ }, |
|
249 |
+ }, |
|
250 |
+ }, |
|
251 |
+ T: field.ErrorTypeRequired, |
|
252 |
+ F: "scopeRestrictions[0].clusterRole.roleNames", |
|
253 |
+ }, |
|
254 |
+ "must have namespaces": { |
|
255 |
+ Client: oapi.OAuthClient{ |
|
256 |
+ ObjectMeta: api.ObjectMeta{Name: "client-name"}, |
|
257 |
+ ScopeRestrictions: []oapi.ScopeRestriction{ |
|
258 |
+ { |
|
259 |
+ ClusterRole: &oapi.ClusterRoleScopeRestriction{RoleNames: []string{"a"}}, |
|
260 |
+ }, |
|
261 |
+ }, |
|
262 |
+ }, |
|
263 |
+ T: field.ErrorTypeRequired, |
|
264 |
+ F: "scopeRestrictions[0].clusterRole.namespaces", |
|
265 |
+ }, |
|
197 | 266 |
} |
198 | 267 |
for k, v := range errorCases { |
199 | 268 |
errs := ValidateClient(&v.Client) |
... | ... |
@@ -14,6 +14,7 @@ import ( |
14 | 14 |
|
15 | 15 |
"github.com/openshift/origin/pkg/oauth/api" |
16 | 16 |
"github.com/openshift/origin/pkg/oauth/registry/oauthaccesstoken" |
17 |
+ "github.com/openshift/origin/pkg/oauth/registry/oauthclient" |
|
17 | 18 |
"github.com/openshift/origin/pkg/util" |
18 | 19 |
"github.com/openshift/origin/pkg/util/observe" |
19 | 20 |
) |
... | ... |
@@ -27,7 +28,7 @@ type REST struct { |
27 | 27 |
const EtcdPrefix = "/oauth/accesstokens" |
28 | 28 |
|
29 | 29 |
// NewREST returns a RESTStorage object that will work against access tokens |
30 |
-func NewREST(s storage.Interface, backends ...storage.Interface) *REST { |
|
30 |
+func NewREST(s storage.Interface, clientGetter oauthclient.Getter, backends ...storage.Interface) *REST { |
|
31 | 31 |
store := &etcdgeneric.Etcd{ |
32 | 32 |
NewFunc: func() runtime.Object { return &api.OAuthAccessToken{} }, |
33 | 33 |
NewListFunc: func() runtime.Object { return &api.OAuthAccessTokenList{} }, |
... | ... |
@@ -53,7 +54,7 @@ func NewREST(s storage.Interface, backends ...storage.Interface) *REST { |
53 | 53 |
Storage: s, |
54 | 54 |
} |
55 | 55 |
|
56 |
- store.CreateStrategy = oauthaccesstoken.Strategy |
|
56 |
+ store.CreateStrategy = oauthaccesstoken.NewStrategy(clientGetter) |
|
57 | 57 |
|
58 | 58 |
if len(backends) > 0 { |
59 | 59 |
// Build identical stores that talk to a single etcd, so we can verify the token is distributed after creation |
... | ... |
@@ -11,16 +11,21 @@ import ( |
11 | 11 |
"k8s.io/kubernetes/pkg/registry/generic" |
12 | 12 |
"k8s.io/kubernetes/pkg/runtime" |
13 | 13 |
"k8s.io/kubernetes/pkg/util/validation/field" |
14 |
+ |
|
15 |
+ scopeauthorizer "github.com/openshift/origin/pkg/authorization/authorizer/scope" |
|
16 |
+ "github.com/openshift/origin/pkg/oauth/registry/oauthclient" |
|
14 | 17 |
) |
15 | 18 |
|
16 | 19 |
// strategy implements behavior for OAuthAccessTokens |
17 | 20 |
type strategy struct { |
18 | 21 |
runtime.ObjectTyper |
22 |
+ |
|
23 |
+ clientGetter oauthclient.Getter |
|
19 | 24 |
} |
20 | 25 |
|
21 |
-// Strategy is the default logic that applies when creating OAuthAccessToken |
|
22 |
-// objects via the REST API. |
|
23 |
-var Strategy = strategy{kapi.Scheme} |
|
26 |
+func NewStrategy(clientGetter oauthclient.Getter) strategy { |
|
27 |
+ return strategy{ObjectTyper: kapi.Scheme, clientGetter: clientGetter} |
|
28 |
+} |
|
24 | 29 |
|
25 | 30 |
func (strategy) PrepareForUpdate(obj, old runtime.Object) {} |
26 | 31 |
|
... | ... |
@@ -37,9 +42,19 @@ func (strategy) PrepareForCreate(obj runtime.Object) { |
37 | 37 |
} |
38 | 38 |
|
39 | 39 |
// Validate validates a new token |
40 |
-func (strategy) Validate(ctx kapi.Context, obj runtime.Object) field.ErrorList { |
|
40 |
+func (s strategy) Validate(ctx kapi.Context, obj runtime.Object) field.ErrorList { |
|
41 | 41 |
token := obj.(*api.OAuthAccessToken) |
42 |
- return validation.ValidateAccessToken(token) |
|
42 |
+ validationErrors := validation.ValidateAccessToken(token) |
|
43 |
+ |
|
44 |
+ client, err := s.clientGetter.GetClient(ctx, token.ClientName) |
|
45 |
+ if err != nil { |
|
46 |
+ return append(validationErrors, field.InternalError(field.NewPath("clientName"), err)) |
|
47 |
+ } |
|
48 |
+ if err := scopeauthorizer.ValidateScopeRestrictions(client, token.Scopes...); err != nil { |
|
49 |
+ return append(validationErrors, field.InternalError(field.NewPath("clientName"), err)) |
|
50 |
+ } |
|
51 |
+ |
|
52 |
+ return validationErrors |
|
43 | 53 |
} |
44 | 54 |
|
45 | 55 |
// AllowCreateOnUpdate is false for OAuth objects |
... | ... |
@@ -14,6 +14,7 @@ import ( |
14 | 14 |
|
15 | 15 |
"github.com/openshift/origin/pkg/oauth/api" |
16 | 16 |
"github.com/openshift/origin/pkg/oauth/registry/oauthauthorizetoken" |
17 |
+ "github.com/openshift/origin/pkg/oauth/registry/oauthclient" |
|
17 | 18 |
"github.com/openshift/origin/pkg/util" |
18 | 19 |
"github.com/openshift/origin/pkg/util/observe" |
19 | 20 |
) |
... | ... |
@@ -27,7 +28,7 @@ type REST struct { |
27 | 27 |
const EtcdPrefix = "/oauth/authorizetokens" |
28 | 28 |
|
29 | 29 |
// NewREST returns a RESTStorage object that will work against authorize tokens |
30 |
-func NewREST(s storage.Interface, backends ...storage.Interface) *REST { |
|
30 |
+func NewREST(s storage.Interface, clientGetter oauthclient.Getter, backends ...storage.Interface) *REST { |
|
31 | 31 |
store := &etcdgeneric.Etcd{ |
32 | 32 |
NewFunc: func() runtime.Object { return &api.OAuthAuthorizeToken{} }, |
33 | 33 |
NewListFunc: func() runtime.Object { return &api.OAuthAuthorizeTokenList{} }, |
... | ... |
@@ -53,7 +54,7 @@ func NewREST(s storage.Interface, backends ...storage.Interface) *REST { |
53 | 53 |
Storage: s, |
54 | 54 |
} |
55 | 55 |
|
56 |
- store.CreateStrategy = oauthauthorizetoken.Strategy |
|
56 |
+ store.CreateStrategy = oauthauthorizetoken.NewStrategy(clientGetter) |
|
57 | 57 |
|
58 | 58 |
if len(backends) > 0 { |
59 | 59 |
// Build identical stores that talk to a single etcd, so we can verify the token is distributed after creation |
... | ... |
@@ -11,16 +11,21 @@ import ( |
11 | 11 |
"k8s.io/kubernetes/pkg/registry/generic" |
12 | 12 |
"k8s.io/kubernetes/pkg/runtime" |
13 | 13 |
"k8s.io/kubernetes/pkg/util/validation/field" |
14 |
+ |
|
15 |
+ scopeauthorizer "github.com/openshift/origin/pkg/authorization/authorizer/scope" |
|
16 |
+ "github.com/openshift/origin/pkg/oauth/registry/oauthclient" |
|
14 | 17 |
) |
15 | 18 |
|
16 | 19 |
// strategy implements behavior for OAuthAuthorizeTokens |
17 | 20 |
type strategy struct { |
18 | 21 |
runtime.ObjectTyper |
22 |
+ |
|
23 |
+ clientGetter oauthclient.Getter |
|
19 | 24 |
} |
20 | 25 |
|
21 |
-// Strategy is the default logic that applies when creating OAuthAuthorizeToken |
|
22 |
-// objects via the REST API. |
|
23 |
-var Strategy = strategy{kapi.Scheme} |
|
26 |
+func NewStrategy(clientGetter oauthclient.Getter) strategy { |
|
27 |
+ return strategy{ObjectTyper: kapi.Scheme, clientGetter: clientGetter} |
|
28 |
+} |
|
24 | 29 |
|
25 | 30 |
func (strategy) PrepareForUpdate(obj, old runtime.Object) {} |
26 | 31 |
|
... | ... |
@@ -41,9 +46,19 @@ func (strategy) Canonicalize(obj runtime.Object) { |
41 | 41 |
} |
42 | 42 |
|
43 | 43 |
// Validate validates a new token |
44 |
-func (strategy) Validate(ctx kapi.Context, obj runtime.Object) field.ErrorList { |
|
44 |
+func (s strategy) Validate(ctx kapi.Context, obj runtime.Object) field.ErrorList { |
|
45 | 45 |
token := obj.(*api.OAuthAuthorizeToken) |
46 |
- return validation.ValidateAuthorizeToken(token) |
|
46 |
+ validationErrors := validation.ValidateAuthorizeToken(token) |
|
47 |
+ |
|
48 |
+ client, err := s.clientGetter.GetClient(ctx, token.ClientName) |
|
49 |
+ if err != nil { |
|
50 |
+ return append(validationErrors, field.InternalError(field.NewPath("clientName"), err)) |
|
51 |
+ } |
|
52 |
+ if err := scopeauthorizer.ValidateScopeRestrictions(client, token.Scopes...); err != nil { |
|
53 |
+ return append(validationErrors, field.InternalError(field.NewPath("clientName"), err)) |
|
54 |
+ } |
|
55 |
+ |
|
56 |
+ return validationErrors |
|
47 | 57 |
} |
48 | 58 |
|
49 | 59 |
// AllowCreateOnUpdate is false for OAuth objects |
... | ... |
@@ -21,6 +21,12 @@ type Registry interface { |
21 | 21 |
DeleteClient(ctx kapi.Context, name string) error |
22 | 22 |
} |
23 | 23 |
|
24 |
+// Getter exposes a way to get a specific client. This is useful for other registries to get scope limitations |
|
25 |
+// on particular clients. This interface will make its easier to write a future cache on it |
|
26 |
+type Getter interface { |
|
27 |
+ GetClient(ctx kapi.Context, name string) (*api.OAuthClient, error) |
|
28 |
+} |
|
29 |
+ |
|
24 | 30 |
// storage puts strong typing around storage calls |
25 | 31 |
type storage struct { |
26 | 32 |
rest.StandardStorage |
... | ... |
@@ -10,6 +10,7 @@ import ( |
10 | 10 |
"k8s.io/kubernetes/pkg/storage" |
11 | 11 |
|
12 | 12 |
"github.com/openshift/origin/pkg/oauth/api" |
13 |
+ "github.com/openshift/origin/pkg/oauth/registry/oauthclient" |
|
13 | 14 |
"github.com/openshift/origin/pkg/oauth/registry/oauthclientauthorization" |
14 | 15 |
"github.com/openshift/origin/pkg/util" |
15 | 16 |
) |
... | ... |
@@ -22,7 +23,7 @@ type REST struct { |
22 | 22 |
const EtcdPrefix = "/oauth/clientauthorizations" |
23 | 23 |
|
24 | 24 |
// NewREST returns a RESTStorage object that will work against oauth clients |
25 |
-func NewREST(s storage.Interface) *REST { |
|
25 |
+func NewREST(s storage.Interface, clientGetter oauthclient.Getter) *REST { |
|
26 | 26 |
store := &etcdgeneric.Etcd{ |
27 | 27 |
NewFunc: func() runtime.Object { return &api.OAuthClientAuthorization{} }, |
28 | 28 |
NewListFunc: func() runtime.Object { return &api.OAuthClientAuthorizationList{} }, |
... | ... |
@@ -43,8 +44,8 @@ func NewREST(s storage.Interface) *REST { |
43 | 43 |
Storage: s, |
44 | 44 |
} |
45 | 45 |
|
46 |
- store.CreateStrategy = oauthclientauthorization.Strategy |
|
47 |
- store.UpdateStrategy = oauthclientauthorization.Strategy |
|
46 |
+ store.CreateStrategy = oauthclientauthorization.NewStrategy(clientGetter) |
|
47 |
+ store.UpdateStrategy = oauthclientauthorization.NewStrategy(clientGetter) |
|
48 | 48 |
|
49 | 49 |
return &REST{*store} |
50 | 50 |
} |
... | ... |
@@ -11,16 +11,21 @@ import ( |
11 | 11 |
"k8s.io/kubernetes/pkg/registry/generic" |
12 | 12 |
"k8s.io/kubernetes/pkg/runtime" |
13 | 13 |
"k8s.io/kubernetes/pkg/util/validation/field" |
14 |
+ |
|
15 |
+ scopeauthorizer "github.com/openshift/origin/pkg/authorization/authorizer/scope" |
|
16 |
+ "github.com/openshift/origin/pkg/oauth/registry/oauthclient" |
|
14 | 17 |
) |
15 | 18 |
|
16 | 19 |
// strategy implements behavior for OAuthClientAuthorization objects |
17 | 20 |
type strategy struct { |
18 | 21 |
runtime.ObjectTyper |
22 |
+ |
|
23 |
+ clientGetter oauthclient.Getter |
|
19 | 24 |
} |
20 | 25 |
|
21 |
-// Strategy is the default logic that applies when creating or updating OAuthClientAuthorization objects |
|
22 |
-// objects via the REST API. |
|
23 |
-var Strategy = strategy{kapi.Scheme} |
|
26 |
+func NewStrategy(clientGetter oauthclient.Getter) strategy { |
|
27 |
+ return strategy{ObjectTyper: kapi.Scheme, clientGetter: clientGetter} |
|
28 |
+} |
|
24 | 29 |
|
25 | 30 |
func (strategy) PrepareForUpdate(obj, old runtime.Object) { |
26 | 31 |
auth := obj.(*api.OAuthClientAuthorization) |
... | ... |
@@ -46,16 +51,36 @@ func (strategy) Canonicalize(obj runtime.Object) { |
46 | 46 |
} |
47 | 47 |
|
48 | 48 |
// Validate validates a new client |
49 |
-func (strategy) Validate(ctx kapi.Context, obj runtime.Object) field.ErrorList { |
|
49 |
+func (s strategy) Validate(ctx kapi.Context, obj runtime.Object) field.ErrorList { |
|
50 | 50 |
auth := obj.(*api.OAuthClientAuthorization) |
51 |
- return validation.ValidateClientAuthorization(auth) |
|
51 |
+ validationErrors := validation.ValidateClientAuthorization(auth) |
|
52 |
+ |
|
53 |
+ client, err := s.clientGetter.GetClient(ctx, auth.ClientName) |
|
54 |
+ if err != nil { |
|
55 |
+ return append(validationErrors, field.InternalError(field.NewPath("clientName"), err)) |
|
56 |
+ } |
|
57 |
+ if err := scopeauthorizer.ValidateScopeRestrictions(client, auth.Scopes...); err != nil { |
|
58 |
+ return append(validationErrors, field.InternalError(field.NewPath("clientName"), err)) |
|
59 |
+ } |
|
60 |
+ |
|
61 |
+ return validationErrors |
|
52 | 62 |
} |
53 | 63 |
|
54 | 64 |
// ValidateUpdate validates a client auth update |
55 |
-func (strategy) ValidateUpdate(ctx kapi.Context, obj runtime.Object, old runtime.Object) field.ErrorList { |
|
65 |
+func (s strategy) ValidateUpdate(ctx kapi.Context, obj runtime.Object, old runtime.Object) field.ErrorList { |
|
56 | 66 |
clientAuth := obj.(*api.OAuthClientAuthorization) |
57 | 67 |
oldClientAuth := old.(*api.OAuthClientAuthorization) |
58 |
- return validation.ValidateClientAuthorizationUpdate(clientAuth, oldClientAuth) |
|
68 |
+ validationErrors := validation.ValidateClientAuthorizationUpdate(clientAuth, oldClientAuth) |
|
69 |
+ |
|
70 |
+ client, err := s.clientGetter.GetClient(ctx, clientAuth.ClientName) |
|
71 |
+ if err != nil { |
|
72 |
+ return append(validationErrors, field.InternalError(field.NewPath("clientName"), err)) |
|
73 |
+ } |
|
74 |
+ if err := scopeauthorizer.ValidateScopeRestrictions(client, clientAuth.Scopes...); err != nil { |
|
75 |
+ return append(validationErrors, field.InternalError(field.NewPath("clientName"), err)) |
|
76 |
+ } |
|
77 |
+ |
|
78 |
+ return validationErrors |
|
59 | 79 |
} |
60 | 80 |
|
61 | 81 |
func (strategy) AllowCreateOnUpdate() bool { |
... | ... |
@@ -34,6 +34,11 @@ func Join(scopes []string) string { |
34 | 34 |
} |
35 | 35 |
|
36 | 36 |
func Covers(has, requested []string) bool { |
37 |
+ // no scopes allows all access, so requesting an empty list is NOT covered by a list with anything in it |
|
38 |
+ if len(requested) == 0 && len(has) > 0 { |
|
39 |
+ return false |
|
40 |
+ } |
|
41 |
+ |
|
37 | 42 |
has, requested = sortAndCopy(has), sortAndCopy(requested) |
38 | 43 |
NextRequested: |
39 | 44 |
for i := range requested { |
... | ... |
@@ -35,8 +35,13 @@ func checkAdd(t *testing.T, s1, s2, expected []string) { |
35 | 35 |
func TestCovers(t *testing.T) { |
36 | 36 |
// Empty request |
37 | 37 |
checkCovers(t, []string{}, []string{}, true) |
38 |
- checkCovers(t, []string{"A"}, []string{}, true) |
|
39 |
- checkCovers(t, []string{"B", "A"}, []string{}, true) |
|
38 |
+ checkCovers(t, []string{"A"}, []string{}, false) |
|
39 |
+ checkCovers(t, []string{"B", "A"}, []string{}, false) |
|
40 |
+ |
|
41 |
+ // empty list is effectively everything and we have validation to keep people from doing an empty list |
|
42 |
+ // but I'm not that we'll keep it like this (might add a "full" scope), so leave it failing in this |
|
43 |
+ // direction too for now. |
|
44 |
+ checkCovers(t, []string{}, []string{"B", "A"}, false) |
|
40 | 45 |
|
41 | 46 |
// Equal request |
42 | 47 |
checkCovers(t, []string{"A"}, []string{"A"}, true) |
... | ... |
@@ -10,6 +10,7 @@ import ( |
10 | 10 |
kerrors "k8s.io/kubernetes/pkg/api/errors" |
11 | 11 |
"k8s.io/kubernetes/pkg/api/unversioned" |
12 | 12 |
|
13 |
+ scopeauthorizer "github.com/openshift/origin/pkg/authorization/authorizer/scope" |
|
13 | 14 |
"github.com/openshift/origin/pkg/oauth/api" |
14 | 15 |
"github.com/openshift/origin/pkg/oauth/registry/oauthaccesstoken" |
15 | 16 |
"github.com/openshift/origin/pkg/oauth/registry/oauthauthorizetoken" |
... | ... |
@@ -186,6 +187,9 @@ func (s *storage) convertFromAuthorizeToken(authorize *api.OAuthAuthorizeToken) |
186 | 186 |
if err != nil { |
187 | 187 |
return nil, err |
188 | 188 |
} |
189 |
+ if err := scopeauthorizer.ValidateScopeRestrictions(client, authorize.Scopes...); err != nil { |
|
190 |
+ return nil, err |
|
191 |
+ } |
|
189 | 192 |
|
190 | 193 |
return &osin.AuthorizeData{ |
191 | 194 |
Code: authorize.Name, |
... | ... |
@@ -229,6 +233,9 @@ func (s *storage) convertFromAccessToken(access *api.OAuthAccessToken) (*osin.Ac |
229 | 229 |
if err != nil { |
230 | 230 |
return nil, err |
231 | 231 |
} |
232 |
+ if err := scopeauthorizer.ValidateScopeRestrictions(client, access.Scopes...); err != nil { |
|
233 |
+ return nil, err |
|
234 |
+ } |
|
232 | 235 |
|
233 | 236 |
return &osin.AccessData{ |
234 | 237 |
AccessToken: access.Name, |
... | ... |
@@ -53,7 +53,7 @@ func TestAuthProxyOnAuthorize(t *testing.T) { |
53 | 53 |
t.Logf("proxy server is on %v\n", proxyServer.URL) |
54 | 54 |
|
55 | 55 |
// need to prime clients so that we can get back a code. the client must be valid |
56 |
- result := clusterAdminClient.RESTClient.Post().Resource("oAuthClients").Body(&oauthapi.OAuthClient{ObjectMeta: kapi.ObjectMeta{Name: "test"}, Secret: "secret", RedirectURIs: []string{clusterAdminClientConfig.Host}}).Do() |
|
56 |
+ result := clusterAdminClient.RESTClient.Post().Resource("oAuthClients").Body(&oauthapi.OAuthClient{ObjectMeta: kapi.ObjectMeta{Name: "test"}, Secret: "secret", RedirectURIs: []string{clusterAdminClientConfig.Host}, AllowAnyScope: true}).Do() |
|
57 | 57 |
checkErr(t, result.Error()) |
58 | 58 |
|
59 | 59 |
// our simple URL to get back a code. We want to go through the front proxy |
... | ... |
@@ -64,13 +64,14 @@ func TestOAuthStorage(t *testing.T) { |
64 | 64 |
} |
65 | 65 |
etcdHelper := etcdstorage.NewEtcdStorage(etcdClient, kapi.Codecs.LegacyCodec(groupMeta.GroupVersions...), etcdtest.PathPrefix(), false) |
66 | 66 |
|
67 |
- accessTokenStorage := accesstokenetcd.NewREST(etcdHelper) |
|
68 |
- accessTokenRegistry := accesstokenregistry.NewRegistry(accessTokenStorage) |
|
69 |
- authorizeTokenStorage := authorizetokenetcd.NewREST(etcdHelper) |
|
70 |
- authorizeTokenRegistry := authorizetokenregistry.NewRegistry(authorizeTokenStorage) |
|
71 | 67 |
clientStorage := clientetcd.NewREST(etcdHelper) |
72 | 68 |
clientRegistry := clientregistry.NewRegistry(clientStorage) |
73 | 69 |
|
70 |
+ accessTokenStorage := accesstokenetcd.NewREST(etcdHelper, clientRegistry) |
|
71 |
+ accessTokenRegistry := accesstokenregistry.NewRegistry(accessTokenStorage) |
|
72 |
+ authorizeTokenStorage := authorizetokenetcd.NewREST(etcdHelper, clientRegistry) |
|
73 |
+ authorizeTokenRegistry := authorizetokenregistry.NewRegistry(authorizeTokenStorage) |
|
74 |
+ |
|
74 | 75 |
user := &testUser{UserName: "test", UserUID: "1"} |
75 | 76 |
storage := registrystorage.New(accessTokenRegistry, authorizeTokenRegistry, clientRegistry, user) |
76 | 77 |
|
... | ... |
@@ -112,9 +113,10 @@ func TestOAuthStorage(t *testing.T) { |
112 | 112 |
})) |
113 | 113 |
|
114 | 114 |
clientRegistry.CreateClient(kapi.NewContext(), &api.OAuthClient{ |
115 |
- ObjectMeta: kapi.ObjectMeta{Name: "test"}, |
|
116 |
- Secret: "secret", |
|
117 |
- RedirectURIs: []string{assertServer.URL + "/assert"}, |
|
115 |
+ ObjectMeta: kapi.ObjectMeta{Name: "test"}, |
|
116 |
+ Secret: "secret", |
|
117 |
+ RedirectURIs: []string{assertServer.URL + "/assert"}, |
|
118 |
+ AllowAnyScope: true, |
|
118 | 119 |
}) |
119 | 120 |
storedClient, err := storage.GetClient("test") |
120 | 121 |
if err != nil { |
... | ... |
@@ -14,6 +14,7 @@ import ( |
14 | 14 |
"github.com/openshift/origin/pkg/authorization/authorizer/scope" |
15 | 15 |
buildapi "github.com/openshift/origin/pkg/build/api" |
16 | 16 |
"github.com/openshift/origin/pkg/client" |
17 |
+ "github.com/openshift/origin/pkg/cmd/server/origin" |
|
17 | 18 |
"github.com/openshift/origin/pkg/cmd/util/clientcmd" |
18 | 19 |
oauthapi "github.com/openshift/origin/pkg/oauth/api" |
19 | 20 |
userapi "github.com/openshift/origin/pkg/user/api" |
... | ... |
@@ -56,7 +57,7 @@ func TestScopedTokens(t *testing.T) { |
56 | 56 |
|
57 | 57 |
whoamiOnlyToken := &oauthapi.OAuthAccessToken{ |
58 | 58 |
ObjectMeta: kapi.ObjectMeta{Name: "whoami-token-plus-some-padding-here-to-make-the-limit"}, |
59 |
- ClientName: "any-client", |
|
59 |
+ ClientName: origin.OpenShiftCLIClientID, |
|
60 | 60 |
ExpiresIn: 200, |
61 | 61 |
Scopes: []string{scope.UserIndicator + scope.UserInfo}, |
62 | 62 |
UserName: userName, |
... | ... |
@@ -176,7 +177,7 @@ func TestScopeEscalations(t *testing.T) { |
176 | 176 |
|
177 | 177 |
nonEscalatingEditToken := &oauthapi.OAuthAccessToken{ |
178 | 178 |
ObjectMeta: kapi.ObjectMeta{Name: "non-escalating-edit-plus-some-padding-here-to-make-the-limit"}, |
179 |
- ClientName: "any-client", |
|
179 |
+ ClientName: origin.OpenShiftCLIClientID, |
|
180 | 180 |
ExpiresIn: 200, |
181 | 181 |
Scopes: []string{scope.ClusterRoleIndicator + "edit:*"}, |
182 | 182 |
UserName: userName, |
... | ... |
@@ -199,7 +200,7 @@ func TestScopeEscalations(t *testing.T) { |
199 | 199 |
|
200 | 200 |
escalatingEditToken := &oauthapi.OAuthAccessToken{ |
201 | 201 |
ObjectMeta: kapi.ObjectMeta{Name: "escalating-edit-plus-some-padding-here-to-make-the-limit"}, |
202 |
- ClientName: "any-client", |
|
202 |
+ ClientName: origin.OpenShiftCLIClientID, |
|
203 | 203 |
ExpiresIn: 200, |
204 | 204 |
Scopes: []string{scope.ClusterRoleIndicator + "edit:*:!"}, |
205 | 205 |
UserName: userName, |