Browse code

add namespace to cluster SAR

deads2k authored on 2015/07/23 02:11:40
Showing 63 changed files
... ...
@@ -18,4 +18,9 @@ when that change will happen.
18 18
 
19 19
 1. The `pauseControllers` field in `master-config.yaml` is deprecated as of Origin 1.0.4 and will
20 20
   no longer be supported in Origin 1.1. After that, a warning will be printed on startup if it
21
-  is set to true.
22 21
\ No newline at end of file
22
+  is set to true.
23
+
24
+1. The `/ns/namespace-name/subjectaccessreview` endpoint is deprecated, use `/subjectaccessreview` 
25
+(with the `namespace` field set) or `/ns/namespace-name/localsubjectaccessreview`.  In 
26
+Origin 1.y / OSE 3.y, support for `/ns/namespace-name/subjectaccessreview` wil be removed.
27
+At that time, the openshift docker registry image must be upgraded in order to continue functioning.
23 28
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Local Resource Access Reviews are objects that allow you to determine which users and groups can perform a given action in a given namespace.
0 1
\ No newline at end of file
1 2
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Local Subject Access Reviews are objects that allow you to determine whether a given user or group can perform a particular action in a given namespace.  Leaving `user` and `groups` empty allows you determine whether the identity making the request can perform the action.
0 1
\ No newline at end of file
... ...
@@ -6837,6 +6837,194 @@
6837 6837
     ]
6838 6838
    },
6839 6839
    {
6840
+    "path": "/oapi/v1/namespaces/{namespace}/localresourceaccessreviews",
6841
+    "description": "OpenShift REST API, version v1",
6842
+    "operations": [
6843
+     {
6844
+      "type": "v1.LocalResourceAccessReview",
6845
+      "method": "POST",
6846
+      "summary": "create a LocalResourceAccessReview",
6847
+      "nickname": "createNamespacedLocalResourceAccessReview",
6848
+      "parameters": [
6849
+       {
6850
+        "type": "string",
6851
+        "paramType": "query",
6852
+        "name": "pretty",
6853
+        "description": "If 'true', then the output is pretty printed.",
6854
+        "required": false,
6855
+        "allowMultiple": false
6856
+       },
6857
+       {
6858
+        "type": "v1.LocalResourceAccessReview",
6859
+        "paramType": "body",
6860
+        "name": "body",
6861
+        "description": "",
6862
+        "required": true,
6863
+        "allowMultiple": false
6864
+       },
6865
+       {
6866
+        "type": "string",
6867
+        "paramType": "path",
6868
+        "name": "namespace",
6869
+        "description": "object name and auth scope, such as for teams and projects",
6870
+        "required": true,
6871
+        "allowMultiple": false
6872
+       }
6873
+      ],
6874
+      "responseMessages": [
6875
+       {
6876
+        "code": 200,
6877
+        "message": "OK",
6878
+        "responseModel": "v1.LocalResourceAccessReview"
6879
+       }
6880
+      ],
6881
+      "produces": [
6882
+       "application/json"
6883
+      ],
6884
+      "consumes": [
6885
+       "*/*"
6886
+      ]
6887
+     }
6888
+    ]
6889
+   },
6890
+   {
6891
+    "path": "/oapi/v1/localresourceaccessreviews",
6892
+    "description": "OpenShift REST API, version v1",
6893
+    "operations": [
6894
+     {
6895
+      "type": "v1.LocalResourceAccessReview",
6896
+      "method": "POST",
6897
+      "summary": "create a LocalResourceAccessReview",
6898
+      "nickname": "createLocalResourceAccessReview",
6899
+      "parameters": [
6900
+       {
6901
+        "type": "string",
6902
+        "paramType": "query",
6903
+        "name": "pretty",
6904
+        "description": "If 'true', then the output is pretty printed.",
6905
+        "required": false,
6906
+        "allowMultiple": false
6907
+       },
6908
+       {
6909
+        "type": "v1.LocalResourceAccessReview",
6910
+        "paramType": "body",
6911
+        "name": "body",
6912
+        "description": "",
6913
+        "required": true,
6914
+        "allowMultiple": false
6915
+       }
6916
+      ],
6917
+      "responseMessages": [
6918
+       {
6919
+        "code": 200,
6920
+        "message": "OK",
6921
+        "responseModel": "v1.LocalResourceAccessReview"
6922
+       }
6923
+      ],
6924
+      "produces": [
6925
+       "application/json"
6926
+      ],
6927
+      "consumes": [
6928
+       "*/*"
6929
+      ]
6930
+     }
6931
+    ]
6932
+   },
6933
+   {
6934
+    "path": "/oapi/v1/namespaces/{namespace}/localsubjectaccessreviews",
6935
+    "description": "OpenShift REST API, version v1",
6936
+    "operations": [
6937
+     {
6938
+      "type": "v1.LocalSubjectAccessReview",
6939
+      "method": "POST",
6940
+      "summary": "create a LocalSubjectAccessReview",
6941
+      "nickname": "createNamespacedLocalSubjectAccessReview",
6942
+      "parameters": [
6943
+       {
6944
+        "type": "string",
6945
+        "paramType": "query",
6946
+        "name": "pretty",
6947
+        "description": "If 'true', then the output is pretty printed.",
6948
+        "required": false,
6949
+        "allowMultiple": false
6950
+       },
6951
+       {
6952
+        "type": "v1.LocalSubjectAccessReview",
6953
+        "paramType": "body",
6954
+        "name": "body",
6955
+        "description": "",
6956
+        "required": true,
6957
+        "allowMultiple": false
6958
+       },
6959
+       {
6960
+        "type": "string",
6961
+        "paramType": "path",
6962
+        "name": "namespace",
6963
+        "description": "object name and auth scope, such as for teams and projects",
6964
+        "required": true,
6965
+        "allowMultiple": false
6966
+       }
6967
+      ],
6968
+      "responseMessages": [
6969
+       {
6970
+        "code": 200,
6971
+        "message": "OK",
6972
+        "responseModel": "v1.LocalSubjectAccessReview"
6973
+       }
6974
+      ],
6975
+      "produces": [
6976
+       "application/json"
6977
+      ],
6978
+      "consumes": [
6979
+       "*/*"
6980
+      ]
6981
+     }
6982
+    ]
6983
+   },
6984
+   {
6985
+    "path": "/oapi/v1/localsubjectaccessreviews",
6986
+    "description": "OpenShift REST API, version v1",
6987
+    "operations": [
6988
+     {
6989
+      "type": "v1.LocalSubjectAccessReview",
6990
+      "method": "POST",
6991
+      "summary": "create a LocalSubjectAccessReview",
6992
+      "nickname": "createLocalSubjectAccessReview",
6993
+      "parameters": [
6994
+       {
6995
+        "type": "string",
6996
+        "paramType": "query",
6997
+        "name": "pretty",
6998
+        "description": "If 'true', then the output is pretty printed.",
6999
+        "required": false,
7000
+        "allowMultiple": false
7001
+       },
7002
+       {
7003
+        "type": "v1.LocalSubjectAccessReview",
7004
+        "paramType": "body",
7005
+        "name": "body",
7006
+        "description": "",
7007
+        "required": true,
7008
+        "allowMultiple": false
7009
+       }
7010
+      ],
7011
+      "responseMessages": [
7012
+       {
7013
+        "code": 200,
7014
+        "message": "OK",
7015
+        "responseModel": "v1.LocalSubjectAccessReview"
7016
+       }
7017
+      ],
7018
+      "produces": [
7019
+       "application/json"
7020
+      ],
7021
+      "consumes": [
7022
+       "*/*"
7023
+      ]
7024
+     }
7025
+    ]
7026
+   },
7027
+   {
6840 7028
     "path": "/oapi/v1/netnamespaces",
6841 7029
     "description": "OpenShift REST API, version v1",
6842 7030
     "operations": [
... ...
@@ -16111,6 +16299,97 @@
16111 16111
      }
16112 16112
     }
16113 16113
    },
16114
+   "v1.LocalResourceAccessReview": {
16115
+    "id": "v1.LocalResourceAccessReview",
16116
+    "required": [
16117
+     "namespace",
16118
+     "verb",
16119
+     "resource",
16120
+     "resourceName"
16121
+    ],
16122
+    "properties": {
16123
+     "kind": {
16124
+      "type": "string",
16125
+      "description": "kind of object, in CamelCase; cannot be updated; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds"
16126
+     },
16127
+     "apiVersion": {
16128
+      "type": "string",
16129
+      "description": "version of the schema the object should have; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources"
16130
+     },
16131
+     "namespace": {
16132
+      "type": "string",
16133
+      "description": "namespace of the action being requested"
16134
+     },
16135
+     "verb": {
16136
+      "type": "string",
16137
+      "description": "one of get, list, watch, create, update, delete"
16138
+     },
16139
+     "resource": {
16140
+      "type": "string",
16141
+      "description": "one of the existing resource types"
16142
+     },
16143
+     "resourceName": {
16144
+      "type": "string",
16145
+      "description": "name of the resource being requested for a get or delete"
16146
+     },
16147
+     "content": {
16148
+      "type": "string",
16149
+      "description": "actual content of the request for create and update"
16150
+     }
16151
+    }
16152
+   },
16153
+   "v1.LocalSubjectAccessReview": {
16154
+    "id": "v1.LocalSubjectAccessReview",
16155
+    "required": [
16156
+     "namespace",
16157
+     "verb",
16158
+     "resource",
16159
+     "resourceName",
16160
+     "user",
16161
+     "groups"
16162
+    ],
16163
+    "properties": {
16164
+     "kind": {
16165
+      "type": "string",
16166
+      "description": "kind of object, in CamelCase; cannot be updated; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds"
16167
+     },
16168
+     "apiVersion": {
16169
+      "type": "string",
16170
+      "description": "version of the schema the object should have; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources"
16171
+     },
16172
+     "namespace": {
16173
+      "type": "string",
16174
+      "description": "namespace of the action being requested"
16175
+     },
16176
+     "verb": {
16177
+      "type": "string",
16178
+      "description": "one of get, list, watch, create, update, delete"
16179
+     },
16180
+     "resource": {
16181
+      "type": "string",
16182
+      "description": "one of the existing resource types"
16183
+     },
16184
+     "resourceName": {
16185
+      "type": "string",
16186
+      "description": "name of the resource being requested for a get or delete"
16187
+     },
16188
+     "content": {
16189
+      "type": "string",
16190
+      "description": "actual content of the request for create and update"
16191
+     },
16192
+     "user": {
16193
+      "type": "string",
16194
+      "description": "optional, if both user and groups are empty, the current authenticated user is used"
16195
+     },
16196
+     "groups": {
16197
+      "type": "array",
16198
+      "items": {
16199
+       "type": "string"
16200
+      },
16201
+      "description": "optional, list of groups to which the user belongs"
16202
+     }
16203
+    }
16204
+   },
16114 16205
    "v1.NetNamespaceList": {
16115 16206
     "id": "v1.NetNamespaceList",
16116 16207
     "required": [
... ...
@@ -16835,8 +17114,10 @@
16835 16835
    "v1.ResourceAccessReview": {
16836 16836
     "id": "v1.ResourceAccessReview",
16837 16837
     "required": [
16838
+     "namespace",
16838 16839
      "verb",
16839
-     "resource"
16840
+     "resource",
16841
+     "resourceName"
16840 16842
     ],
16841 16843
     "properties": {
16842 16844
      "kind": {
... ...
@@ -16847,6 +17128,10 @@
16847 16847
       "type": "string",
16848 16848
       "description": "version of the schema the object should have; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources"
16849 16849
      },
16850
+     "namespace": {
16851
+      "type": "string",
16852
+      "description": "namespace of the action being requested"
16853
+     },
16850 16854
      "verb": {
16851 16855
       "type": "string",
16852 16856
       "description": "one of get, list, watch, create, update, delete"
... ...
@@ -16855,13 +17140,13 @@
16855 16855
       "type": "string",
16856 16856
       "description": "one of the existing resource types"
16857 16857
      },
16858
-     "content": {
16858
+     "resourceName": {
16859 16859
       "type": "string",
16860
-      "description": "actual content of the request for a create or update"
16860
+      "description": "name of the resource being requested for a get or delete"
16861 16861
      },
16862
-     "resourceName": {
16862
+     "content": {
16863 16863
       "type": "string",
16864
-      "description": "name of the resource being requested for a get or delete operation"
16864
+      "description": "actual content of the request for create and update"
16865 16865
      }
16866 16866
     }
16867 16867
    },
... ...
@@ -17002,11 +17287,12 @@
17002 17002
    "v1.SubjectAccessReview": {
17003 17003
     "id": "v1.SubjectAccessReview",
17004 17004
     "required": [
17005
+     "namespace",
17005 17006
      "verb",
17006 17007
      "resource",
17008
+     "resourceName",
17007 17009
      "user",
17008
-     "groups",
17009
-     "resourceName"
17010
+     "groups"
17010 17011
     ],
17011 17012
     "properties": {
17012 17013
      "kind": {
... ...
@@ -17017,6 +17303,10 @@
17017 17017
       "type": "string",
17018 17018
       "description": "version of the schema the object should have; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources"
17019 17019
      },
17020
+     "namespace": {
17021
+      "type": "string",
17022
+      "description": "namespace of the action being requested"
17023
+     },
17020 17024
      "verb": {
17021 17025
       "type": "string",
17022 17026
       "description": "one of get, list, watch, create, update, delete"
... ...
@@ -17025,6 +17315,14 @@
17025 17025
       "type": "string",
17026 17026
       "description": "one of the existing resource types"
17027 17027
      },
17028
+     "resourceName": {
17029
+      "type": "string",
17030
+      "description": "name of the resource being requested for a get or delete"
17031
+     },
17032
+     "content": {
17033
+      "type": "string",
17034
+      "description": "actual content of the request for create and update"
17035
+     },
17028 17036
      "user": {
17029 17037
       "type": "string",
17030 17038
       "description": "optional, if both user and groups are empty, the current authenticated user is used"
... ...
@@ -17035,14 +17333,6 @@
17035 17035
        "type": "string"
17036 17036
       },
17037 17037
       "description": "optional, list of groups to which the user belongs"
17038
-     },
17039
-     "content": {
17040
-      "type": "string",
17041
-      "description": "actual content of the request for create and update"
17042
-     },
17043
-     "resourceName": {
17044
-      "type": "string",
17045
-      "description": "name of the resource being requested for a get or delete"
17046 17038
      }
17047 17039
     }
17048 17040
    },
... ...
@@ -18,6 +18,19 @@ import (
18 18
 	util "k8s.io/kubernetes/pkg/util"
19 19
 )
20 20
 
21
+func deepCopy_api_AuthorizationAttributes(in api.AuthorizationAttributes, out *api.AuthorizationAttributes, c *conversion.Cloner) error {
22
+	out.Namespace = in.Namespace
23
+	out.Verb = in.Verb
24
+	out.Resource = in.Resource
25
+	out.ResourceName = in.ResourceName
26
+	if newVal, err := c.DeepCopy(in.Content); err != nil {
27
+		return err
28
+	} else {
29
+		out.Content = newVal.(runtime.EmbeddedObject)
30
+	}
31
+	return nil
32
+}
33
+
21 34
 func deepCopy_api_ClusterPolicy(in api.ClusterPolicy, out *api.ClusterPolicy, c *conversion.Cloner) error {
22 35
 	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
23 36
 		return err
... ...
@@ -257,6 +270,43 @@ func deepCopy_api_IsPersonalSubjectAccessReview(in api.IsPersonalSubjectAccessRe
257 257
 	return nil
258 258
 }
259 259
 
260
+func deepCopy_api_LocalResourceAccessReview(in api.LocalResourceAccessReview, out *api.LocalResourceAccessReview, c *conversion.Cloner) error {
261
+	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
262
+		return err
263
+	} else {
264
+		out.TypeMeta = newVal.(pkgapi.TypeMeta)
265
+	}
266
+	if err := deepCopy_api_AuthorizationAttributes(in.Action, &out.Action, c); err != nil {
267
+		return err
268
+	}
269
+	return nil
270
+}
271
+
272
+func deepCopy_api_LocalSubjectAccessReview(in api.LocalSubjectAccessReview, out *api.LocalSubjectAccessReview, c *conversion.Cloner) error {
273
+	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
274
+		return err
275
+	} else {
276
+		out.TypeMeta = newVal.(pkgapi.TypeMeta)
277
+	}
278
+	if err := deepCopy_api_AuthorizationAttributes(in.Action, &out.Action, c); err != nil {
279
+		return err
280
+	}
281
+	out.User = in.User
282
+	if in.Groups != nil {
283
+		out.Groups = make(util.StringSet)
284
+		for key, val := range in.Groups {
285
+			if newVal, err := c.DeepCopy(val); err != nil {
286
+				return err
287
+			} else {
288
+				out.Groups[key] = newVal.(util.Empty)
289
+			}
290
+		}
291
+	} else {
292
+		out.Groups = nil
293
+	}
294
+	return nil
295
+}
296
+
260 297
 func deepCopy_api_Policy(in api.Policy, out *api.Policy, c *conversion.Cloner) error {
261 298
 	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
262 299
 		return err
... ...
@@ -435,14 +485,9 @@ func deepCopy_api_ResourceAccessReview(in api.ResourceAccessReview, out *api.Res
435 435
 	} else {
436 436
 		out.TypeMeta = newVal.(pkgapi.TypeMeta)
437 437
 	}
438
-	out.Verb = in.Verb
439
-	out.Resource = in.Resource
440
-	if newVal, err := c.DeepCopy(in.Content); err != nil {
438
+	if err := deepCopy_api_AuthorizationAttributes(in.Action, &out.Action, c); err != nil {
441 439
 		return err
442
-	} else {
443
-		out.Content = newVal.(runtime.EmbeddedObject)
444 440
 	}
445
-	out.ResourceName = in.ResourceName
446 441
 	return nil
447 442
 }
448 443
 
... ...
@@ -601,8 +646,9 @@ func deepCopy_api_SubjectAccessReview(in api.SubjectAccessReview, out *api.Subje
601 601
 	} else {
602 602
 		out.TypeMeta = newVal.(pkgapi.TypeMeta)
603 603
 	}
604
-	out.Verb = in.Verb
605
-	out.Resource = in.Resource
604
+	if err := deepCopy_api_AuthorizationAttributes(in.Action, &out.Action, c); err != nil {
605
+		return err
606
+	}
606 607
 	out.User = in.User
607 608
 	if in.Groups != nil {
608 609
 		out.Groups = make(util.StringSet)
... ...
@@ -616,12 +662,6 @@ func deepCopy_api_SubjectAccessReview(in api.SubjectAccessReview, out *api.Subje
616 616
 	} else {
617 617
 		out.Groups = nil
618 618
 	}
619
-	if newVal, err := c.DeepCopy(in.Content); err != nil {
620
-		return err
621
-	} else {
622
-		out.Content = newVal.(runtime.EmbeddedObject)
623
-	}
624
-	out.ResourceName = in.ResourceName
625 619
 	return nil
626 620
 }
627 621
 
... ...
@@ -2549,6 +2589,7 @@ func deepCopy_api_UserList(in userapi.UserList, out *userapi.UserList, c *conver
2549 2549
 
2550 2550
 func init() {
2551 2551
 	err := pkgapi.Scheme.AddGeneratedDeepCopyFuncs(
2552
+		deepCopy_api_AuthorizationAttributes,
2552 2553
 		deepCopy_api_ClusterPolicy,
2553 2554
 		deepCopy_api_ClusterPolicyBinding,
2554 2555
 		deepCopy_api_ClusterPolicyBindingList,
... ...
@@ -2558,6 +2599,8 @@ func init() {
2558 2558
 		deepCopy_api_ClusterRoleBindingList,
2559 2559
 		deepCopy_api_ClusterRoleList,
2560 2560
 		deepCopy_api_IsPersonalSubjectAccessReview,
2561
+		deepCopy_api_LocalResourceAccessReview,
2562
+		deepCopy_api_LocalSubjectAccessReview,
2561 2563
 		deepCopy_api_Policy,
2562 2564
 		deepCopy_api_PolicyBinding,
2563 2565
 		deepCopy_api_PolicyBindingList,
... ...
@@ -200,22 +200,6 @@ func convert_api_PolicyList_To_v1_PolicyList(in *api.PolicyList, out *v1.PolicyL
200 200
 	return nil
201 201
 }
202 202
 
203
-func convert_api_ResourceAccessReview_To_v1_ResourceAccessReview(in *api.ResourceAccessReview, out *v1.ResourceAccessReview, s conversion.Scope) error {
204
-	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
205
-		defaulting.(func(*api.ResourceAccessReview))(in)
206
-	}
207
-	if err := convert_api_TypeMeta_To_v1_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
208
-		return err
209
-	}
210
-	out.Verb = in.Verb
211
-	out.Resource = in.Resource
212
-	if err := s.Convert(&in.Content, &out.Content, 0); err != nil {
213
-		return err
214
-	}
215
-	out.ResourceName = in.ResourceName
216
-	return nil
217
-}
218
-
219 203
 func convert_api_Role_To_v1_Role(in *api.Role, out *v1.Role, s conversion.Scope) error {
220 204
 	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
221 205
 		defaulting.(func(*api.Role))(in)
... ...
@@ -469,22 +453,6 @@ func convert_v1_PolicyList_To_api_PolicyList(in *v1.PolicyList, out *api.PolicyL
469 469
 	return nil
470 470
 }
471 471
 
472
-func convert_v1_ResourceAccessReview_To_api_ResourceAccessReview(in *v1.ResourceAccessReview, out *api.ResourceAccessReview, s conversion.Scope) error {
473
-	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
474
-		defaulting.(func(*v1.ResourceAccessReview))(in)
475
-	}
476
-	if err := convert_v1_TypeMeta_To_api_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
477
-		return err
478
-	}
479
-	out.Verb = in.Verb
480
-	out.Resource = in.Resource
481
-	if err := s.Convert(&in.Content, &out.Content, 0); err != nil {
482
-		return err
483
-	}
484
-	out.ResourceName = in.ResourceName
485
-	return nil
486
-}
487
-
488 472
 func convert_v1_Role_To_api_Role(in *v1.Role, out *api.Role, s conversion.Scope) error {
489 473
 	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
490 474
 		defaulting.(func(*v1.Role))(in)
... ...
@@ -3196,7 +3164,6 @@ func init() {
3196 3196
 		convert_api_ProjectSpec_To_v1_ProjectSpec,
3197 3197
 		convert_api_ProjectStatus_To_v1_ProjectStatus,
3198 3198
 		convert_api_Project_To_v1_Project,
3199
-		convert_api_ResourceAccessReview_To_v1_ResourceAccessReview,
3200 3199
 		convert_api_ResourceRequirements_To_v1_ResourceRequirements,
3201 3200
 		convert_api_RoleBindingList_To_v1_RoleBindingList,
3202 3201
 		convert_api_RoleList_To_v1_RoleList,
... ...
@@ -3274,7 +3241,6 @@ func init() {
3274 3274
 		convert_v1_ProjectSpec_To_api_ProjectSpec,
3275 3275
 		convert_v1_ProjectStatus_To_api_ProjectStatus,
3276 3276
 		convert_v1_Project_To_api_Project,
3277
-		convert_v1_ResourceAccessReview_To_api_ResourceAccessReview,
3278 3277
 		convert_v1_ResourceRequirements_To_api_ResourceRequirements,
3279 3278
 		convert_v1_RoleBindingList_To_api_RoleBindingList,
3280 3279
 		convert_v1_RoleList_To_api_RoleList,
... ...
@@ -19,6 +19,19 @@ import (
19 19
 	util "k8s.io/kubernetes/pkg/util"
20 20
 )
21 21
 
22
+func deepCopy_v1_AuthorizationAttributes(in v1.AuthorizationAttributes, out *v1.AuthorizationAttributes, c *conversion.Cloner) error {
23
+	out.Namespace = in.Namespace
24
+	out.Verb = in.Verb
25
+	out.Resource = in.Resource
26
+	out.ResourceName = in.ResourceName
27
+	if newVal, err := c.DeepCopy(in.Content); err != nil {
28
+		return err
29
+	} else {
30
+		out.Content = newVal.(runtime.RawExtension)
31
+	}
32
+	return nil
33
+}
34
+
22 35
 func deepCopy_v1_ClusterPolicy(in v1.ClusterPolicy, out *v1.ClusterPolicy, c *conversion.Cloner) error {
23 36
 	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
24 37
 		return err
... ...
@@ -246,6 +259,39 @@ func deepCopy_v1_IsPersonalSubjectAccessReview(in v1.IsPersonalSubjectAccessRevi
246 246
 	return nil
247 247
 }
248 248
 
249
+func deepCopy_v1_LocalResourceAccessReview(in v1.LocalResourceAccessReview, out *v1.LocalResourceAccessReview, c *conversion.Cloner) error {
250
+	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
251
+		return err
252
+	} else {
253
+		out.TypeMeta = newVal.(pkgapiv1.TypeMeta)
254
+	}
255
+	if err := deepCopy_v1_AuthorizationAttributes(in.AuthorizationAttributes, &out.AuthorizationAttributes, c); err != nil {
256
+		return err
257
+	}
258
+	return nil
259
+}
260
+
261
+func deepCopy_v1_LocalSubjectAccessReview(in v1.LocalSubjectAccessReview, out *v1.LocalSubjectAccessReview, c *conversion.Cloner) error {
262
+	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
263
+		return err
264
+	} else {
265
+		out.TypeMeta = newVal.(pkgapiv1.TypeMeta)
266
+	}
267
+	if err := deepCopy_v1_AuthorizationAttributes(in.AuthorizationAttributes, &out.AuthorizationAttributes, c); err != nil {
268
+		return err
269
+	}
270
+	out.User = in.User
271
+	if in.GroupsSlice != nil {
272
+		out.GroupsSlice = make([]string, len(in.GroupsSlice))
273
+		for i := range in.GroupsSlice {
274
+			out.GroupsSlice[i] = in.GroupsSlice[i]
275
+		}
276
+	} else {
277
+		out.GroupsSlice = nil
278
+	}
279
+	return nil
280
+}
281
+
249 282
 func deepCopy_v1_NamedClusterRole(in v1.NamedClusterRole, out *v1.NamedClusterRole, c *conversion.Cloner) error {
250 283
 	out.Name = in.Name
251 284
 	if err := deepCopy_v1_ClusterRole(in.Role, &out.Role, c); err != nil {
... ...
@@ -436,14 +482,9 @@ func deepCopy_v1_ResourceAccessReview(in v1.ResourceAccessReview, out *v1.Resour
436 436
 	} else {
437 437
 		out.TypeMeta = newVal.(pkgapiv1.TypeMeta)
438 438
 	}
439
-	out.Verb = in.Verb
440
-	out.Resource = in.Resource
441
-	if newVal, err := c.DeepCopy(in.Content); err != nil {
439
+	if err := deepCopy_v1_AuthorizationAttributes(in.AuthorizationAttributes, &out.AuthorizationAttributes, c); err != nil {
442 440
 		return err
443
-	} else {
444
-		out.Content = newVal.(runtime.RawExtension)
445 441
 	}
446
-	out.ResourceName = in.ResourceName
447 442
 	return nil
448 443
 }
449 444
 
... ...
@@ -586,8 +627,9 @@ func deepCopy_v1_SubjectAccessReview(in v1.SubjectAccessReview, out *v1.SubjectA
586 586
 	} else {
587 587
 		out.TypeMeta = newVal.(pkgapiv1.TypeMeta)
588 588
 	}
589
-	out.Verb = in.Verb
590
-	out.Resource = in.Resource
589
+	if err := deepCopy_v1_AuthorizationAttributes(in.AuthorizationAttributes, &out.AuthorizationAttributes, c); err != nil {
590
+		return err
591
+	}
591 592
 	out.User = in.User
592 593
 	if in.GroupsSlice != nil {
593 594
 		out.GroupsSlice = make([]string, len(in.GroupsSlice))
... ...
@@ -597,12 +639,6 @@ func deepCopy_v1_SubjectAccessReview(in v1.SubjectAccessReview, out *v1.SubjectA
597 597
 	} else {
598 598
 		out.GroupsSlice = nil
599 599
 	}
600
-	if newVal, err := c.DeepCopy(in.Content); err != nil {
601
-		return err
602
-	} else {
603
-		out.Content = newVal.(runtime.RawExtension)
604
-	}
605
-	out.ResourceName = in.ResourceName
606 600
 	return nil
607 601
 }
608 602
 
... ...
@@ -2426,6 +2462,7 @@ func deepCopy_v1_UserList(in userapiv1.UserList, out *userapiv1.UserList, c *con
2426 2426
 
2427 2427
 func init() {
2428 2428
 	err := api.Scheme.AddGeneratedDeepCopyFuncs(
2429
+		deepCopy_v1_AuthorizationAttributes,
2429 2430
 		deepCopy_v1_ClusterPolicy,
2430 2431
 		deepCopy_v1_ClusterPolicyBinding,
2431 2432
 		deepCopy_v1_ClusterPolicyBindingList,
... ...
@@ -2435,6 +2472,8 @@ func init() {
2435 2435
 		deepCopy_v1_ClusterRoleBindingList,
2436 2436
 		deepCopy_v1_ClusterRoleList,
2437 2437
 		deepCopy_v1_IsPersonalSubjectAccessReview,
2438
+		deepCopy_v1_LocalResourceAccessReview,
2439
+		deepCopy_v1_LocalSubjectAccessReview,
2438 2440
 		deepCopy_v1_NamedClusterRole,
2439 2441
 		deepCopy_v1_NamedClusterRoleBinding,
2440 2442
 		deepCopy_v1_NamedRole,
... ...
@@ -219,22 +219,6 @@ func convert_api_PolicyList_To_v1beta3_PolicyList(in *api.PolicyList, out *v1bet
219 219
 	return nil
220 220
 }
221 221
 
222
-func convert_api_ResourceAccessReview_To_v1beta3_ResourceAccessReview(in *api.ResourceAccessReview, out *v1beta3.ResourceAccessReview, s conversion.Scope) error {
223
-	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
224
-		defaulting.(func(*api.ResourceAccessReview))(in)
225
-	}
226
-	if err := convert_api_TypeMeta_To_v1beta3_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
227
-		return err
228
-	}
229
-	out.Verb = in.Verb
230
-	out.Resource = in.Resource
231
-	if err := s.Convert(&in.Content, &out.Content, 0); err != nil {
232
-		return err
233
-	}
234
-	out.ResourceName = in.ResourceName
235
-	return nil
236
-}
237
-
238 222
 func convert_api_Role_To_v1beta3_Role(in *api.Role, out *v1beta3.Role, s conversion.Scope) error {
239 223
 	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
240 224
 		defaulting.(func(*api.Role))(in)
... ...
@@ -507,22 +491,6 @@ func convert_v1beta3_PolicyList_To_api_PolicyList(in *v1beta3.PolicyList, out *a
507 507
 	return nil
508 508
 }
509 509
 
510
-func convert_v1beta3_ResourceAccessReview_To_api_ResourceAccessReview(in *v1beta3.ResourceAccessReview, out *api.ResourceAccessReview, s conversion.Scope) error {
511
-	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
512
-		defaulting.(func(*v1beta3.ResourceAccessReview))(in)
513
-	}
514
-	if err := convert_v1beta3_TypeMeta_To_api_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
515
-		return err
516
-	}
517
-	out.Verb = in.Verb
518
-	out.Resource = in.Resource
519
-	if err := s.Convert(&in.Content, &out.Content, 0); err != nil {
520
-		return err
521
-	}
522
-	out.ResourceName = in.ResourceName
523
-	return nil
524
-}
525
-
526 510
 func convert_v1beta3_Role_To_api_Role(in *v1beta3.Role, out *api.Role, s conversion.Scope) error {
527 511
 	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
528 512
 		defaulting.(func(*v1beta3.Role))(in)
... ...
@@ -3130,7 +3098,6 @@ func init() {
3130 3130
 		convert_api_ProjectSpec_To_v1beta3_ProjectSpec,
3131 3131
 		convert_api_ProjectStatus_To_v1beta3_ProjectStatus,
3132 3132
 		convert_api_Project_To_v1beta3_Project,
3133
-		convert_api_ResourceAccessReview_To_v1beta3_ResourceAccessReview,
3134 3133
 		convert_api_ResourceRequirements_To_v1beta3_ResourceRequirements,
3135 3134
 		convert_api_RoleBindingList_To_v1beta3_RoleBindingList,
3136 3135
 		convert_api_RoleList_To_v1beta3_RoleList,
... ...
@@ -3206,7 +3173,6 @@ func init() {
3206 3206
 		convert_v1beta3_ProjectSpec_To_api_ProjectSpec,
3207 3207
 		convert_v1beta3_ProjectStatus_To_api_ProjectStatus,
3208 3208
 		convert_v1beta3_Project_To_api_Project,
3209
-		convert_v1beta3_ResourceAccessReview_To_api_ResourceAccessReview,
3210 3209
 		convert_v1beta3_ResourceRequirements_To_api_ResourceRequirements,
3211 3210
 		convert_v1beta3_RoleBindingList_To_api_RoleBindingList,
3212 3211
 		convert_v1beta3_RoleList_To_api_RoleList,
... ...
@@ -19,6 +19,19 @@ import (
19 19
 	util "k8s.io/kubernetes/pkg/util"
20 20
 )
21 21
 
22
+func deepCopy_v1beta3_AuthorizationAttributes(in v1beta3.AuthorizationAttributes, out *v1beta3.AuthorizationAttributes, c *conversion.Cloner) error {
23
+	out.Namespace = in.Namespace
24
+	out.Verb = in.Verb
25
+	out.Resource = in.Resource
26
+	out.ResourceName = in.ResourceName
27
+	if newVal, err := c.DeepCopy(in.Content); err != nil {
28
+		return err
29
+	} else {
30
+		out.Content = newVal.(runtime.RawExtension)
31
+	}
32
+	return nil
33
+}
34
+
22 35
 func deepCopy_v1beta3_ClusterPolicy(in v1beta3.ClusterPolicy, out *v1beta3.ClusterPolicy, c *conversion.Cloner) error {
23 36
 	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
24 37
 		return err
... ...
@@ -246,6 +259,39 @@ func deepCopy_v1beta3_IsPersonalSubjectAccessReview(in v1beta3.IsPersonalSubject
246 246
 	return nil
247 247
 }
248 248
 
249
+func deepCopy_v1beta3_LocalResourceAccessReview(in v1beta3.LocalResourceAccessReview, out *v1beta3.LocalResourceAccessReview, c *conversion.Cloner) error {
250
+	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
251
+		return err
252
+	} else {
253
+		out.TypeMeta = newVal.(pkgapiv1beta3.TypeMeta)
254
+	}
255
+	if err := deepCopy_v1beta3_AuthorizationAttributes(in.AuthorizationAttributes, &out.AuthorizationAttributes, c); err != nil {
256
+		return err
257
+	}
258
+	return nil
259
+}
260
+
261
+func deepCopy_v1beta3_LocalSubjectAccessReview(in v1beta3.LocalSubjectAccessReview, out *v1beta3.LocalSubjectAccessReview, c *conversion.Cloner) error {
262
+	if newVal, err := c.DeepCopy(in.TypeMeta); err != nil {
263
+		return err
264
+	} else {
265
+		out.TypeMeta = newVal.(pkgapiv1beta3.TypeMeta)
266
+	}
267
+	if err := deepCopy_v1beta3_AuthorizationAttributes(in.AuthorizationAttributes, &out.AuthorizationAttributes, c); err != nil {
268
+		return err
269
+	}
270
+	out.User = in.User
271
+	if in.GroupsSlice != nil {
272
+		out.GroupsSlice = make([]string, len(in.GroupsSlice))
273
+		for i := range in.GroupsSlice {
274
+			out.GroupsSlice[i] = in.GroupsSlice[i]
275
+		}
276
+	} else {
277
+		out.GroupsSlice = nil
278
+	}
279
+	return nil
280
+}
281
+
249 282
 func deepCopy_v1beta3_NamedClusterRole(in v1beta3.NamedClusterRole, out *v1beta3.NamedClusterRole, c *conversion.Cloner) error {
250 283
 	out.Name = in.Name
251 284
 	if err := deepCopy_v1beta3_ClusterRole(in.Role, &out.Role, c); err != nil {
... ...
@@ -444,14 +490,9 @@ func deepCopy_v1beta3_ResourceAccessReview(in v1beta3.ResourceAccessReview, out
444 444
 	} else {
445 445
 		out.TypeMeta = newVal.(pkgapiv1beta3.TypeMeta)
446 446
 	}
447
-	out.Verb = in.Verb
448
-	out.Resource = in.Resource
449
-	if newVal, err := c.DeepCopy(in.Content); err != nil {
447
+	if err := deepCopy_v1beta3_AuthorizationAttributes(in.AuthorizationAttributes, &out.AuthorizationAttributes, c); err != nil {
450 448
 		return err
451
-	} else {
452
-		out.Content = newVal.(runtime.RawExtension)
453 449
 	}
454
-	out.ResourceName = in.ResourceName
455 450
 	return nil
456 451
 }
457 452
 
... ...
@@ -594,8 +635,9 @@ func deepCopy_v1beta3_SubjectAccessReview(in v1beta3.SubjectAccessReview, out *v
594 594
 	} else {
595 595
 		out.TypeMeta = newVal.(pkgapiv1beta3.TypeMeta)
596 596
 	}
597
-	out.Verb = in.Verb
598
-	out.Resource = in.Resource
597
+	if err := deepCopy_v1beta3_AuthorizationAttributes(in.AuthorizationAttributes, &out.AuthorizationAttributes, c); err != nil {
598
+		return err
599
+	}
599 600
 	out.User = in.User
600 601
 	if in.GroupsSlice != nil {
601 602
 		out.GroupsSlice = make([]string, len(in.GroupsSlice))
... ...
@@ -605,12 +647,6 @@ func deepCopy_v1beta3_SubjectAccessReview(in v1beta3.SubjectAccessReview, out *v
605 605
 	} else {
606 606
 		out.GroupsSlice = nil
607 607
 	}
608
-	if newVal, err := c.DeepCopy(in.Content); err != nil {
609
-		return err
610
-	} else {
611
-		out.Content = newVal.(runtime.RawExtension)
612
-	}
613
-	out.ResourceName = in.ResourceName
614 608
 	return nil
615 609
 }
616 610
 
... ...
@@ -2416,6 +2452,7 @@ func deepCopy_v1beta3_UserList(in userapiv1beta3.UserList, out *userapiv1beta3.U
2416 2416
 
2417 2417
 func init() {
2418 2418
 	err := api.Scheme.AddGeneratedDeepCopyFuncs(
2419
+		deepCopy_v1beta3_AuthorizationAttributes,
2419 2420
 		deepCopy_v1beta3_ClusterPolicy,
2420 2421
 		deepCopy_v1beta3_ClusterPolicyBinding,
2421 2422
 		deepCopy_v1beta3_ClusterPolicyBindingList,
... ...
@@ -2425,6 +2462,8 @@ func init() {
2425 2425
 		deepCopy_v1beta3_ClusterRoleBindingList,
2426 2426
 		deepCopy_v1beta3_ClusterRoleList,
2427 2427
 		deepCopy_v1beta3_IsPersonalSubjectAccessReview,
2428
+		deepCopy_v1beta3_LocalResourceAccessReview,
2429
+		deepCopy_v1beta3_LocalSubjectAccessReview,
2428 2430
 		deepCopy_v1beta3_NamedClusterRole,
2429 2431
 		deepCopy_v1beta3_NamedClusterRoleBinding,
2430 2432
 		deepCopy_v1beta3_NamedRole,
... ...
@@ -27,6 +27,8 @@ import (
27 27
 func init() {
28 28
 	Validator.Register(&authorizationapi.SubjectAccessReview{}, authorizationvalidation.ValidateSubjectAccessReview, nil)
29 29
 	Validator.Register(&authorizationapi.ResourceAccessReview{}, authorizationvalidation.ValidateResourceAccessReview, nil)
30
+	Validator.Register(&authorizationapi.LocalSubjectAccessReview{}, authorizationvalidation.ValidateLocalSubjectAccessReview, nil)
31
+	Validator.Register(&authorizationapi.LocalResourceAccessReview{}, authorizationvalidation.ValidateLocalResourceAccessReview, nil)
30 32
 
31 33
 	Validator.Register(&authorizationapi.Policy{}, authorizationvalidation.ValidateLocalPolicy, authorizationvalidation.ValidateLocalPolicyUpdate)
32 34
 	Validator.Register(&authorizationapi.PolicyBinding{}, authorizationvalidation.ValidateLocalPolicyBinding, authorizationvalidation.ValidateLocalPolicyBindingUpdate)
... ...
@@ -10,15 +10,17 @@ func init() {
10 10
 		&RoleBinding{},
11 11
 		&Policy{},
12 12
 		&PolicyBinding{},
13
-		&ResourceAccessReview{},
14
-		&SubjectAccessReview{},
15
-		&ResourceAccessReviewResponse{},
16
-		&SubjectAccessReviewResponse{},
17 13
 		&PolicyList{},
18 14
 		&PolicyBindingList{},
19 15
 		&RoleBindingList{},
20 16
 		&RoleList{},
21 17
 
18
+		&ResourceAccessReview{},
19
+		&SubjectAccessReview{},
20
+		&LocalResourceAccessReview{},
21
+		&LocalSubjectAccessReview{},
22
+		&ResourceAccessReviewResponse{},
23
+		&SubjectAccessReviewResponse{},
22 24
 		&IsPersonalSubjectAccessReview{},
23 25
 
24 26
 		&ClusterRole{},
... ...
@@ -41,17 +43,19 @@ func (*ClusterPolicyBindingList) IsAnAPIObject() {}
41 41
 func (*ClusterRoleBindingList) IsAnAPIObject()   {}
42 42
 func (*ClusterRoleList) IsAnAPIObject()          {}
43 43
 
44
-func (*Role) IsAnAPIObject()                         {}
45
-func (*Policy) IsAnAPIObject()                       {}
46
-func (*PolicyBinding) IsAnAPIObject()                {}
47
-func (*RoleBinding) IsAnAPIObject()                  {}
48
-func (*ResourceAccessReview) IsAnAPIObject()         {}
49
-func (*SubjectAccessReview) IsAnAPIObject()          {}
50
-func (*ResourceAccessReviewResponse) IsAnAPIObject() {}
51
-func (*SubjectAccessReviewResponse) IsAnAPIObject()  {}
52
-func (*PolicyList) IsAnAPIObject()                   {}
53
-func (*PolicyBindingList) IsAnAPIObject()            {}
54
-func (*RoleBindingList) IsAnAPIObject()              {}
55
-func (*RoleList) IsAnAPIObject()                     {}
44
+func (*Role) IsAnAPIObject()              {}
45
+func (*Policy) IsAnAPIObject()            {}
46
+func (*PolicyBinding) IsAnAPIObject()     {}
47
+func (*RoleBinding) IsAnAPIObject()       {}
48
+func (*PolicyList) IsAnAPIObject()        {}
49
+func (*PolicyBindingList) IsAnAPIObject() {}
50
+func (*RoleBindingList) IsAnAPIObject()   {}
51
+func (*RoleList) IsAnAPIObject()          {}
56 52
 
53
+func (*ResourceAccessReview) IsAnAPIObject()          {}
54
+func (*SubjectAccessReview) IsAnAPIObject()           {}
55
+func (*LocalResourceAccessReview) IsAnAPIObject()     {}
56
+func (*LocalSubjectAccessReview) IsAnAPIObject()      {}
57
+func (*ResourceAccessReviewResponse) IsAnAPIObject()  {}
58
+func (*SubjectAccessReviewResponse) IsAnAPIObject()   {}
57 59
 func (*IsPersonalSubjectAccessReview) IsAnAPIObject() {}
... ...
@@ -64,15 +64,18 @@ const (
64 64
 
65 65
 var (
66 66
 	GroupsToResources = map[string][]string{
67
-		BuildGroupName:              {"builds", "buildconfigs", "buildlogs", "buildconfigs/instantiate", "builds/log", "builds/clone", "buildconfigs/webhooks"},
68
-		ImageGroupName:              {"imagestreams", "imagestreammappings", "imagestreamtags", "imagestreamimages"},
69
-		DeploymentGroupName:         {"deployments", "deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks"},
70
-		SDNGroupName:                {"clusternetworks", "hostsubnets", "netnamespaces"},
71
-		TemplateGroupName:           {"templates", "templateconfigs", "processedtemplates"},
72
-		UserGroupName:               {"identities", "users", "useridentitymappings", "groups"},
73
-		OAuthGroupName:              {"oauthauthorizetokens", "oauthaccesstokens", "oauthclients", "oauthclientauthorizations"},
74
-		PolicyOwnerGroupName:        {"policies", "policybindings"},
75
-		PermissionGrantingGroupName: {"roles", "rolebindings", "resourceaccessreviews", "subjectaccessreviews"},
67
+		BuildGroupName:       {"builds", "buildconfigs", "buildlogs", "buildconfigs/instantiate", "builds/log", "builds/clone", "buildconfigs/webhooks"},
68
+		ImageGroupName:       {"imagestreams", "imagestreammappings", "imagestreamtags", "imagestreamimages"},
69
+		DeploymentGroupName:  {"deployments", "deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks"},
70
+		SDNGroupName:         {"clusternetworks", "hostsubnets", "netnamespaces"},
71
+		TemplateGroupName:    {"templates", "templateconfigs", "processedtemplates"},
72
+		UserGroupName:        {"identities", "users", "useridentitymappings", "groups"},
73
+		OAuthGroupName:       {"oauthauthorizetokens", "oauthaccesstokens", "oauthclients", "oauthclientauthorizations"},
74
+		PolicyOwnerGroupName: {"policies", "policybindings"},
75
+
76
+		// RAR and SAR are in this list to support backwards compatibility with clients that expect access to those resource in a namespace scope and a cluster scope.
77
+		// TODO remove once we have eliminated the namespace scoped resource.
78
+		PermissionGrantingGroupName: {"roles", "rolebindings", "resourceaccessreviews" /* cluster scoped*/, "subjectaccessreviews" /* cluster scoped*/, "localresourceaccessreviews", "localsubjectaccessreviews"},
76 79
 		OpenshiftExposedGroupName:   {BuildGroupName, ImageGroupName, DeploymentGroupName, TemplateGroupName, "routes"},
77 80
 		OpenshiftAllGroupName: {OpenshiftExposedGroupName, UserGroupName, OAuthGroupName, PolicyOwnerGroupName, SDNGroupName, PermissionGrantingGroupName, OpenshiftStatusGroupName, "projects",
78 81
 			"clusterroles", "clusterrolebindings", "clusterpolicies", "clusterpolicybindings", "images" /* cluster scoped*/, "projectrequests"},
... ...
@@ -195,14 +198,8 @@ type ResourceAccessReviewResponse struct {
195 195
 type ResourceAccessReview struct {
196 196
 	kapi.TypeMeta
197 197
 
198
-	// Verb is one of: get, list, watch, create, update, delete
199
-	Verb string
200
-	// Resource is one of the existing resource types
201
-	Resource string
202
-	// Content is the actual content of the request for create and update
203
-	Content kruntime.EmbeddedObject
204
-	// ResourceName is the name of the resource being requested for a "get" or deleted for a "delete"
205
-	ResourceName string
198
+	// Action describes the action being tested
199
+	Action AuthorizationAttributes
206 200
 }
207 201
 
208 202
 // SubjectAccessReviewResponse describes whether or not a user or group can perform an action
... ...
@@ -221,18 +218,45 @@ type SubjectAccessReviewResponse struct {
221 221
 type SubjectAccessReview struct {
222 222
 	kapi.TypeMeta
223 223
 
224
-	// Verb is one of: get, list, watch, create, update, delete
225
-	Verb string
226
-	// Resource is one of the existing resource types
227
-	Resource string
224
+	// Action describes the action being tested
225
+	Action AuthorizationAttributes
228 226
 	// User is optional.  If both User and Groups are empty, the current authenticated user is used.
229 227
 	User string
230 228
 	// Groups is optional.  Groups is the list of groups to which the User belongs.
231 229
 	Groups util.StringSet
232
-	// Content is the actual content of the request for create and update
233
-	Content kruntime.EmbeddedObject
230
+}
231
+
232
+// LocalResourceAccessReview is a means to request a list of which users and groups are authorized to perform the action specified by spec in a particular namespace
233
+type LocalResourceAccessReview struct {
234
+	kapi.TypeMeta
235
+
236
+	// Action describes the action being tested
237
+	Action AuthorizationAttributes
238
+}
239
+
240
+// LocalSubjectAccessReview is an object for requesting information about whether a user or group can perform an action in a particular namespace
241
+type LocalSubjectAccessReview struct {
242
+	kapi.TypeMeta
243
+
244
+	// Action describes the action being tested.  The Namespace element is FORCED to the current namespace.
245
+	Action AuthorizationAttributes
246
+	// User is optional.  If both User and Groups are empty, the current authenticated user is used.
247
+	User string
248
+	// Groups is optional.  Groups is the list of groups to which the User belongs.
249
+	Groups util.StringSet
250
+}
251
+
252
+type AuthorizationAttributes struct {
253
+	// Namespace is the namespace of the action being requested.  Currently, there is no distinction between no namespace and all namespaces
254
+	Namespace string
255
+	// Verb is one of: get, list, watch, create, update, delete
256
+	Verb string
257
+	// Resource is one of the existing resource types
258
+	Resource string
234 259
 	// ResourceName is the name of the resource being requested for a "get" or deleted for a "delete"
235 260
 	ResourceName string
261
+	// Content is the actual content of the request for create and update
262
+	Content kruntime.EmbeddedObject
236 263
 }
237 264
 
238 265
 // PolicyList is a collection of Policies
... ...
@@ -10,10 +10,57 @@ import (
10 10
 	newer "github.com/openshift/origin/pkg/authorization/api"
11 11
 )
12 12
 
13
+func convert_v1_ResourceAccessReview_To_api_ResourceAccessReview(in *ResourceAccessReview, out *newer.ResourceAccessReview, s conversion.Scope) error {
14
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
15
+		return err
16
+	}
17
+	if err := s.DefaultConvert(&in.AuthorizationAttributes, &out.Action, conversion.IgnoreMissingFields); err != nil {
18
+		return err
19
+	}
20
+
21
+	return nil
22
+}
23
+
24
+func convert_api_ResourceAccessReview_To_v1_ResourceAccessReview(in *newer.ResourceAccessReview, out *ResourceAccessReview, s conversion.Scope) error {
25
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
26
+		return err
27
+	}
28
+	if err := s.DefaultConvert(&in.Action, &out.AuthorizationAttributes, conversion.IgnoreMissingFields); err != nil {
29
+		return err
30
+	}
31
+
32
+	return nil
33
+}
34
+
35
+func convert_v1_LocalResourceAccessReview_To_api_LocalResourceAccessReview(in *LocalResourceAccessReview, out *newer.LocalResourceAccessReview, s conversion.Scope) error {
36
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
37
+		return err
38
+	}
39
+	if err := s.DefaultConvert(&in.AuthorizationAttributes, &out.Action, conversion.IgnoreMissingFields); err != nil {
40
+		return err
41
+	}
42
+
43
+	return nil
44
+}
45
+
46
+func convert_api_LocalResourceAccessReview_To_v1_LocalResourceAccessReview(in *newer.LocalResourceAccessReview, out *LocalResourceAccessReview, s conversion.Scope) error {
47
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
48
+		return err
49
+	}
50
+	if err := s.DefaultConvert(&in.Action, &out.AuthorizationAttributes, conversion.IgnoreMissingFields); err != nil {
51
+		return err
52
+	}
53
+
54
+	return nil
55
+}
56
+
13 57
 func convert_v1_SubjectAccessReview_To_api_SubjectAccessReview(in *SubjectAccessReview, out *newer.SubjectAccessReview, s conversion.Scope) error {
14 58
 	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
15 59
 		return err
16 60
 	}
61
+	if err := s.DefaultConvert(&in.AuthorizationAttributes, &out.Action, conversion.IgnoreMissingFields); err != nil {
62
+		return err
63
+	}
17 64
 
18 65
 	out.Groups = util.NewStringSet(in.GroupsSlice...)
19 66
 
... ...
@@ -24,6 +71,35 @@ func convert_api_SubjectAccessReview_To_v1_SubjectAccessReview(in *newer.Subject
24 24
 	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
25 25
 		return err
26 26
 	}
27
+	if err := s.DefaultConvert(&in.Action, &out.AuthorizationAttributes, conversion.IgnoreMissingFields); err != nil {
28
+		return err
29
+	}
30
+
31
+	out.GroupsSlice = in.Groups.List()
32
+
33
+	return nil
34
+}
35
+
36
+func convert_v1_LocalSubjectAccessReview_To_api_LocalSubjectAccessReview(in *LocalSubjectAccessReview, out *newer.LocalSubjectAccessReview, s conversion.Scope) error {
37
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
38
+		return err
39
+	}
40
+	if err := s.DefaultConvert(&in.AuthorizationAttributes, &out.Action, conversion.IgnoreMissingFields); err != nil {
41
+		return err
42
+	}
43
+
44
+	out.Groups = util.NewStringSet(in.GroupsSlice...)
45
+
46
+	return nil
47
+}
48
+
49
+func convert_api_LocalSubjectAccessReview_To_v1_LocalSubjectAccessReview(in *newer.LocalSubjectAccessReview, out *LocalSubjectAccessReview, s conversion.Scope) error {
50
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
51
+		return err
52
+	}
53
+	if err := s.DefaultConvert(&in.Action, &out.AuthorizationAttributes, conversion.IgnoreMissingFields); err != nil {
54
+		return err
55
+	}
27 56
 
28 57
 	out.GroupsSlice = in.Groups.List()
29 58
 
... ...
@@ -312,6 +388,12 @@ func init() {
312 312
 
313 313
 		convert_v1_SubjectAccessReview_To_api_SubjectAccessReview,
314 314
 		convert_api_SubjectAccessReview_To_v1_SubjectAccessReview,
315
+		convert_v1_LocalSubjectAccessReview_To_api_LocalSubjectAccessReview,
316
+		convert_api_LocalSubjectAccessReview_To_v1_LocalSubjectAccessReview,
317
+		convert_v1_ResourceAccessReview_To_api_ResourceAccessReview,
318
+		convert_api_ResourceAccessReview_To_v1_ResourceAccessReview,
319
+		convert_v1_LocalResourceAccessReview_To_api_LocalResourceAccessReview,
320
+		convert_api_LocalResourceAccessReview_To_v1_LocalResourceAccessReview,
315 321
 		convert_v1_ResourceAccessReviewResponse_To_api_ResourceAccessReviewResponse,
316 322
 		convert_api_ResourceAccessReviewResponse_To_v1_ResourceAccessReviewResponse,
317 323
 		convert_v1_PolicyRule_To_api_PolicyRule,
... ...
@@ -10,15 +10,17 @@ func init() {
10 10
 		&RoleBinding{},
11 11
 		&Policy{},
12 12
 		&PolicyBinding{},
13
-		&ResourceAccessReview{},
14
-		&SubjectAccessReview{},
15
-		&ResourceAccessReviewResponse{},
16
-		&SubjectAccessReviewResponse{},
17 13
 		&PolicyList{},
18 14
 		&PolicyBindingList{},
19 15
 		&RoleBindingList{},
20 16
 		&RoleList{},
21 17
 
18
+		&ResourceAccessReview{},
19
+		&SubjectAccessReview{},
20
+		&LocalResourceAccessReview{},
21
+		&LocalSubjectAccessReview{},
22
+		&ResourceAccessReviewResponse{},
23
+		&SubjectAccessReviewResponse{},
22 24
 		&IsPersonalSubjectAccessReview{},
23 25
 
24 26
 		&ClusterRole{},
... ...
@@ -41,17 +43,19 @@ func (*ClusterPolicyBindingList) IsAnAPIObject() {}
41 41
 func (*ClusterRoleBindingList) IsAnAPIObject()   {}
42 42
 func (*ClusterRoleList) IsAnAPIObject()          {}
43 43
 
44
-func (*Role) IsAnAPIObject()                         {}
45
-func (*Policy) IsAnAPIObject()                       {}
46
-func (*PolicyBinding) IsAnAPIObject()                {}
47
-func (*RoleBinding) IsAnAPIObject()                  {}
48
-func (*ResourceAccessReview) IsAnAPIObject()         {}
49
-func (*SubjectAccessReview) IsAnAPIObject()          {}
50
-func (*ResourceAccessReviewResponse) IsAnAPIObject() {}
51
-func (*SubjectAccessReviewResponse) IsAnAPIObject()  {}
52
-func (*PolicyList) IsAnAPIObject()                   {}
53
-func (*PolicyBindingList) IsAnAPIObject()            {}
54
-func (*RoleBindingList) IsAnAPIObject()              {}
55
-func (*RoleList) IsAnAPIObject()                     {}
44
+func (*Role) IsAnAPIObject()              {}
45
+func (*Policy) IsAnAPIObject()            {}
46
+func (*PolicyBinding) IsAnAPIObject()     {}
47
+func (*RoleBinding) IsAnAPIObject()       {}
48
+func (*PolicyList) IsAnAPIObject()        {}
49
+func (*PolicyBindingList) IsAnAPIObject() {}
50
+func (*RoleBindingList) IsAnAPIObject()   {}
51
+func (*RoleList) IsAnAPIObject()          {}
56 52
 
53
+func (*ResourceAccessReview) IsAnAPIObject()          {}
54
+func (*SubjectAccessReview) IsAnAPIObject()           {}
55
+func (*LocalResourceAccessReview) IsAnAPIObject()     {}
56
+func (*LocalSubjectAccessReview) IsAnAPIObject()      {}
57
+func (*ResourceAccessReviewResponse) IsAnAPIObject()  {}
58
+func (*SubjectAccessReviewResponse) IsAnAPIObject()   {}
57 59
 func (*IsPersonalSubjectAccessReview) IsAnAPIObject() {}
... ...
@@ -90,6 +90,16 @@ type PolicyBinding struct {
90 90
 	RoleBindings []NamedRoleBinding `json:"roleBindings" description:"all roleBindings held by this policyBinding"`
91 91
 }
92 92
 
93
+type NamedRole struct {
94
+	Name string `json:"name" description:"name of the role"`
95
+	Role Role   `json:"role" description:"the role"`
96
+}
97
+
98
+type NamedRoleBinding struct {
99
+	Name        string      `json:"name" description:"name of the roleBinding"`
100
+	RoleBinding RoleBinding `json:"roleBinding" description:"the roleBinding"`
101
+}
102
+
93 103
 // ResourceAccessReviewResponse describes who can perform the action
94 104
 type ResourceAccessReviewResponse struct {
95 105
 	kapi.TypeMeta `json:",inline"`
... ...
@@ -107,24 +117,8 @@ type ResourceAccessReviewResponse struct {
107 107
 type ResourceAccessReview struct {
108 108
 	kapi.TypeMeta `json:",inline"`
109 109
 
110
-	// Verb is one of: get, list, watch, create, update, delete
111
-	Verb string `json:"verb" description:"one of get, list, watch, create, update, delete"`
112
-	// Resource is one of the existing resource types
113
-	Resource string `json:"resource" description:"one of the existing resource types"`
114
-	// Content is the actual content of the request for create and update
115
-	Content kruntime.RawExtension `json:"content,omitempty" description:"actual content of the request for a create or update"`
116
-	// ResourceName is the name of the resource being requested for a "get" or deleted for a "delete"
117
-	ResourceName string `json:"resourceName,omitempty" description:"name of the resource being requested for a get or delete operation"`
118
-}
119
-
120
-type NamedRole struct {
121
-	Name string `json:"name" description:"name of the role"`
122
-	Role Role   `json:"role" description:"the role"`
123
-}
124
-
125
-type NamedRoleBinding struct {
126
-	Name        string      `json:"name" description:"name of the roleBinding"`
127
-	RoleBinding RoleBinding `json:"roleBinding" description:"the roleBinding"`
110
+	// AuthorizationAttributes describes the action being tested.
111
+	AuthorizationAttributes `json:",inline" description:"the action being tested"`
128 112
 }
129 113
 
130 114
 // SubjectAccessReviewResponse describes whether or not a user or group can perform an action
... ...
@@ -143,18 +137,45 @@ type SubjectAccessReviewResponse struct {
143 143
 type SubjectAccessReview struct {
144 144
 	kapi.TypeMeta `json:",inline"`
145 145
 
146
-	// Verb is one of: get, list, watch, create, update, delete
147
-	Verb string `json:"verb" description:"one of get, list, watch, create, update, delete"`
148
-	// Resource is one of the existing resource types
149
-	Resource string `json:"resource" description:"one of the existing resource types"`
146
+	// AuthorizationAttributes describes the action being tested.
147
+	AuthorizationAttributes `json:",inline" description:"the action being tested"`
150 148
 	// User is optional. If both User and Groups are empty, the current authenticated user is used.
151 149
 	User string `json:"user" description:"optional, if both user and groups are empty, the current authenticated user is used"`
152 150
 	// GroupsSlice is optional. Groups is the list of groups to which the User belongs.
153 151
 	GroupsSlice []string `json:"groups" description:"optional, list of groups to which the user belongs"`
154
-	// Content is the actual content of the request for create and update
155
-	Content kruntime.RawExtension `json:"content,omitempty" description:"actual content of the request for create and update"`
152
+}
153
+
154
+// LocalResourceAccessReview is a means to request a list of which users and groups are authorized to perform the action specified by spec in a particular namespace
155
+type LocalResourceAccessReview struct {
156
+	kapi.TypeMeta `json:",inline"`
157
+
158
+	// AuthorizationAttributes describes the action being tested.  The Namespace element is FORCED to the current namespace.
159
+	AuthorizationAttributes `json:",inline" description:"the action being tested"`
160
+}
161
+
162
+// LocalSubjectAccessReview is an object for requesting information about whether a user or group can perform an action in a particular namespace
163
+type LocalSubjectAccessReview struct {
164
+	kapi.TypeMeta
165
+
166
+	// AuthorizationAttributes describes the action being tested.  The Namespace element is FORCED to the current namespace.
167
+	AuthorizationAttributes `json:",inline" description:"the action being tested"`
168
+	// User is optional.  If both User and Groups are empty, the current authenticated user is used.
169
+	User string `json:"user" description:"optional, if both user and groups are empty, the current authenticated user is used"`
170
+	// Groups is optional.  Groups is the list of groups to which the User belongs.
171
+	GroupsSlice []string `json:"groups" description:"optional, list of groups to which the user belongs"`
172
+}
173
+
174
+type AuthorizationAttributes struct {
175
+	// Namespace is the namespace of the action being requested.  Currently, there is no distinction between no namespace and all namespaces
176
+	Namespace string `json:"namespace" description:"namespace of the action being requested"`
177
+	// Verb is one of: get, list, watch, create, update, delete
178
+	Verb string `json:"verb" description:"one of get, list, watch, create, update, delete"`
179
+	// Resource is one of the existing resource types
180
+	Resource string `json:"resource" description:"one of the existing resource types"`
156 181
 	// ResourceName is the name of the resource being requested for a "get" or deleted for a "delete"
157 182
 	ResourceName string `json:"resourceName" description:"name of the resource being requested for a get or delete"`
183
+	// Content is the actual content of the request for create and update
184
+	Content kruntime.RawExtension `json:"content,omitempty" description:"actual content of the request for create and update"`
158 185
 }
159 186
 
160 187
 // PolicyList is a collection of Policies
... ...
@@ -10,10 +10,57 @@ import (
10 10
 	newer "github.com/openshift/origin/pkg/authorization/api"
11 11
 )
12 12
 
13
+func convert_v1beta3_ResourceAccessReview_To_api_ResourceAccessReview(in *ResourceAccessReview, out *newer.ResourceAccessReview, s conversion.Scope) error {
14
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
15
+		return err
16
+	}
17
+	if err := s.DefaultConvert(&in.AuthorizationAttributes, &out.Action, conversion.IgnoreMissingFields); err != nil {
18
+		return err
19
+	}
20
+
21
+	return nil
22
+}
23
+
24
+func convert_api_ResourceAccessReview_To_v1beta3_ResourceAccessReview(in *newer.ResourceAccessReview, out *ResourceAccessReview, s conversion.Scope) error {
25
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
26
+		return err
27
+	}
28
+	if err := s.DefaultConvert(&in.Action, &out.AuthorizationAttributes, conversion.IgnoreMissingFields); err != nil {
29
+		return err
30
+	}
31
+
32
+	return nil
33
+}
34
+
35
+func convert_v1beta3_LocalResourceAccessReview_To_api_LocalResourceAccessReview(in *LocalResourceAccessReview, out *newer.LocalResourceAccessReview, s conversion.Scope) error {
36
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
37
+		return err
38
+	}
39
+	if err := s.DefaultConvert(&in.AuthorizationAttributes, &out.Action, conversion.IgnoreMissingFields); err != nil {
40
+		return err
41
+	}
42
+
43
+	return nil
44
+}
45
+
46
+func convert_api_LocalResourceAccessReview_To_v1beta3_LocalResourceAccessReview(in *newer.LocalResourceAccessReview, out *LocalResourceAccessReview, s conversion.Scope) error {
47
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
48
+		return err
49
+	}
50
+	if err := s.DefaultConvert(&in.Action, &out.AuthorizationAttributes, conversion.IgnoreMissingFields); err != nil {
51
+		return err
52
+	}
53
+
54
+	return nil
55
+}
56
+
13 57
 func convert_v1beta3_SubjectAccessReview_To_api_SubjectAccessReview(in *SubjectAccessReview, out *newer.SubjectAccessReview, s conversion.Scope) error {
14 58
 	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
15 59
 		return err
16 60
 	}
61
+	if err := s.DefaultConvert(&in.AuthorizationAttributes, &out.Action, conversion.IgnoreMissingFields); err != nil {
62
+		return err
63
+	}
17 64
 
18 65
 	out.Groups = util.NewStringSet(in.GroupsSlice...)
19 66
 
... ...
@@ -24,6 +71,35 @@ func convert_api_SubjectAccessReview_To_v1beta3_SubjectAccessReview(in *newer.Su
24 24
 	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
25 25
 		return err
26 26
 	}
27
+	if err := s.DefaultConvert(&in.Action, &out.AuthorizationAttributes, conversion.IgnoreMissingFields); err != nil {
28
+		return err
29
+	}
30
+
31
+	out.GroupsSlice = in.Groups.List()
32
+
33
+	return nil
34
+}
35
+
36
+func convert_v1beta3_LocalSubjectAccessReview_To_api_LocalSubjectAccessReview(in *LocalSubjectAccessReview, out *newer.LocalSubjectAccessReview, s conversion.Scope) error {
37
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
38
+		return err
39
+	}
40
+	if err := s.DefaultConvert(&in.AuthorizationAttributes, &out.Action, conversion.IgnoreMissingFields); err != nil {
41
+		return err
42
+	}
43
+
44
+	out.Groups = util.NewStringSet(in.GroupsSlice...)
45
+
46
+	return nil
47
+}
48
+
49
+func convert_api_LocalSubjectAccessReview_To_v1beta3_LocalSubjectAccessReview(in *newer.LocalSubjectAccessReview, out *LocalSubjectAccessReview, s conversion.Scope) error {
50
+	if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
51
+		return err
52
+	}
53
+	if err := s.DefaultConvert(&in.Action, &out.AuthorizationAttributes, conversion.IgnoreMissingFields); err != nil {
54
+		return err
55
+	}
27 56
 
28 57
 	out.GroupsSlice = in.Groups.List()
29 58
 
... ...
@@ -186,6 +262,12 @@ func init() {
186 186
 	err := api.Scheme.AddConversionFuncs(
187 187
 		convert_v1beta3_SubjectAccessReview_To_api_SubjectAccessReview,
188 188
 		convert_api_SubjectAccessReview_To_v1beta3_SubjectAccessReview,
189
+		convert_v1beta3_LocalSubjectAccessReview_To_api_LocalSubjectAccessReview,
190
+		convert_api_LocalSubjectAccessReview_To_v1beta3_LocalSubjectAccessReview,
191
+		convert_v1beta3_ResourceAccessReview_To_api_ResourceAccessReview,
192
+		convert_api_ResourceAccessReview_To_v1beta3_ResourceAccessReview,
193
+		convert_v1beta3_LocalResourceAccessReview_To_api_LocalResourceAccessReview,
194
+		convert_api_LocalResourceAccessReview_To_v1beta3_LocalResourceAccessReview,
189 195
 		convert_v1beta3_ResourceAccessReviewResponse_To_api_ResourceAccessReviewResponse,
190 196
 		convert_api_ResourceAccessReviewResponse_To_v1beta3_ResourceAccessReviewResponse,
191 197
 		convert_v1beta3_PolicyRule_To_api_PolicyRule,
... ...
@@ -10,15 +10,17 @@ func init() {
10 10
 		&RoleBinding{},
11 11
 		&Policy{},
12 12
 		&PolicyBinding{},
13
-		&ResourceAccessReview{},
14
-		&SubjectAccessReview{},
15
-		&ResourceAccessReviewResponse{},
16
-		&SubjectAccessReviewResponse{},
17 13
 		&PolicyList{},
18 14
 		&PolicyBindingList{},
19 15
 		&RoleBindingList{},
20 16
 		&RoleList{},
21 17
 
18
+		&ResourceAccessReview{},
19
+		&SubjectAccessReview{},
20
+		&LocalResourceAccessReview{},
21
+		&LocalSubjectAccessReview{},
22
+		&ResourceAccessReviewResponse{},
23
+		&SubjectAccessReviewResponse{},
22 24
 		&IsPersonalSubjectAccessReview{},
23 25
 
24 26
 		&ClusterRole{},
... ...
@@ -41,17 +43,19 @@ func (*ClusterPolicyBindingList) IsAnAPIObject() {}
41 41
 func (*ClusterRoleBindingList) IsAnAPIObject()   {}
42 42
 func (*ClusterRoleList) IsAnAPIObject()          {}
43 43
 
44
-func (*Role) IsAnAPIObject()                         {}
45
-func (*Policy) IsAnAPIObject()                       {}
46
-func (*PolicyBinding) IsAnAPIObject()                {}
47
-func (*RoleBinding) IsAnAPIObject()                  {}
48
-func (*ResourceAccessReview) IsAnAPIObject()         {}
49
-func (*SubjectAccessReview) IsAnAPIObject()          {}
50
-func (*ResourceAccessReviewResponse) IsAnAPIObject() {}
51
-func (*SubjectAccessReviewResponse) IsAnAPIObject()  {}
52
-func (*PolicyList) IsAnAPIObject()                   {}
53
-func (*PolicyBindingList) IsAnAPIObject()            {}
54
-func (*RoleBindingList) IsAnAPIObject()              {}
55
-func (*RoleList) IsAnAPIObject()                     {}
44
+func (*Role) IsAnAPIObject()              {}
45
+func (*Policy) IsAnAPIObject()            {}
46
+func (*PolicyBinding) IsAnAPIObject()     {}
47
+func (*RoleBinding) IsAnAPIObject()       {}
48
+func (*PolicyList) IsAnAPIObject()        {}
49
+func (*PolicyBindingList) IsAnAPIObject() {}
50
+func (*RoleBindingList) IsAnAPIObject()   {}
51
+func (*RoleList) IsAnAPIObject()          {}
56 52
 
53
+func (*ResourceAccessReview) IsAnAPIObject()          {}
54
+func (*SubjectAccessReview) IsAnAPIObject()           {}
55
+func (*LocalResourceAccessReview) IsAnAPIObject()     {}
56
+func (*LocalSubjectAccessReview) IsAnAPIObject()      {}
57
+func (*ResourceAccessReviewResponse) IsAnAPIObject()  {}
58
+func (*SubjectAccessReviewResponse) IsAnAPIObject()   {}
57 59
 func (*IsPersonalSubjectAccessReview) IsAnAPIObject() {}
... ...
@@ -93,6 +93,16 @@ type PolicyBinding struct {
93 93
 	RoleBindings []NamedRoleBinding `json:"roleBindings"`
94 94
 }
95 95
 
96
+type NamedRole struct {
97
+	Name string `json:"name"`
98
+	Role Role   `json:"role"`
99
+}
100
+
101
+type NamedRoleBinding struct {
102
+	Name        string      `json:"name"`
103
+	RoleBinding RoleBinding `json:"roleBinding"`
104
+}
105
+
96 106
 // ResourceAccessReviewResponse describes who can perform the action
97 107
 type ResourceAccessReviewResponse struct {
98 108
 	kapi.TypeMeta `json:",inline"`
... ...
@@ -110,24 +120,8 @@ type ResourceAccessReviewResponse struct {
110 110
 type ResourceAccessReview struct {
111 111
 	kapi.TypeMeta `json:",inline"`
112 112
 
113
-	// Verb is one of: get, list, watch, create, update, delete
114
-	Verb string `json:"verb"`
115
-	// Resource is one of the existing resource types
116
-	Resource string `json:"resource"`
117
-	// Content is the actual content of the request for create and update
118
-	Content kruntime.RawExtension `json:"content,omitempty"`
119
-	// ResourceName is the name of the resource being requested for a "get" or deleted for a "delete"
120
-	ResourceName string `json:"resourceName,omitempty"`
121
-}
122
-
123
-type NamedRole struct {
124
-	Name string `json:"name"`
125
-	Role Role   `json:"role"`
126
-}
127
-
128
-type NamedRoleBinding struct {
129
-	Name        string      `json:"name"`
130
-	RoleBinding RoleBinding `json:"roleBinding"`
113
+	// AuthorizationAttributes describes the action being tested
114
+	AuthorizationAttributes `json:",inline"`
131 115
 }
132 116
 
133 117
 // SubjectAccessReviewResponse describes whether or not a user or group can perform an action
... ...
@@ -146,18 +140,45 @@ type SubjectAccessReviewResponse struct {
146 146
 type SubjectAccessReview struct {
147 147
 	kapi.TypeMeta `json:",inline"`
148 148
 
149
-	// Verb is one of: get, list, watch, create, update, delete
150
-	Verb string `json:"verb"`
151
-	// Resource is one of the existing resource types
152
-	Resource string `json:"resource"`
149
+	// AuthorizationAttributes describes the action being tested
150
+	AuthorizationAttributes `json:",inline"`
153 151
 	// User is optional.  If both User and Groups are empty, the current authenticated user is used.
154 152
 	User string `json:"user"`
155 153
 	// Groups is optional.  Groups is the list of groups to which the User belongs.
156 154
 	GroupsSlice []string `json:"groups"`
157
-	// Content is the actual content of the request for create and update
158
-	Content kruntime.RawExtension `json:"content,omitempty"`
155
+}
156
+
157
+// LocalResourceAccessReview is a means to request a list of which users and groups are authorized to perform the action specified by spec in a particular namespace
158
+type LocalResourceAccessReview struct {
159
+	kapi.TypeMeta `json:",inline"`
160
+
161
+	// AuthorizationAttributes describes the action being tested.  The Namespace element is FORCED to the current namespace.
162
+	AuthorizationAttributes `json:",inline"`
163
+}
164
+
165
+// LocalSubjectAccessReview is an object for requesting information about whether a user or group can perform an action in a particular namespace
166
+type LocalSubjectAccessReview struct {
167
+	kapi.TypeMeta `json:",inline"`
168
+
169
+	// AuthorizationAttributes describes the action being tested.  The Namespace element is FORCED to the current namespace.
170
+	AuthorizationAttributes `json:",inline"`
171
+	// User is optional.  If both User and Groups are empty, the current authenticated user is used.
172
+	User string `json:"user"`
173
+	// Groups is optional.  Groups is the list of groups to which the User belongs.
174
+	GroupsSlice []string `json:"groups"`
175
+}
176
+
177
+type AuthorizationAttributes struct {
178
+	// Namespace is the namespace of the action being requested.  Currently, there is no distinction between no namespace and all namespaces
179
+	Namespace string `json:"namespace"`
180
+	// Verb is one of: get, list, watch, create, update, delete
181
+	Verb string `json:"verb"`
182
+	// Resource is one of the existing resource types
183
+	Resource string `json:"resource"`
159 184
 	// ResourceName is the name of the resource being requested for a "get" or deleted for a "delete"
160 185
 	ResourceName string `json:"resourceName"`
186
+	// Content is the actual content of the request for create and update
187
+	Content kruntime.RawExtension `json:"content,omitempty"`
161 188
 }
162 189
 
163 190
 // PolicyList is a collection of Policies
... ...
@@ -12,10 +12,10 @@ import (
12 12
 func ValidateSubjectAccessReview(review *authorizationapi.SubjectAccessReview) fielderrors.ValidationErrorList {
13 13
 	allErrs := fielderrors.ValidationErrorList{}
14 14
 
15
-	if len(review.Verb) == 0 {
15
+	if len(review.Action.Verb) == 0 {
16 16
 		allErrs = append(allErrs, fielderrors.NewFieldRequired("verb"))
17 17
 	}
18
-	if len(review.Resource) == 0 {
18
+	if len(review.Action.Resource) == 0 {
19 19
 		allErrs = append(allErrs, fielderrors.NewFieldRequired("resource"))
20 20
 	}
21 21
 
... ...
@@ -25,10 +25,36 @@ func ValidateSubjectAccessReview(review *authorizationapi.SubjectAccessReview) f
25 25
 func ValidateResourceAccessReview(review *authorizationapi.ResourceAccessReview) fielderrors.ValidationErrorList {
26 26
 	allErrs := fielderrors.ValidationErrorList{}
27 27
 
28
-	if len(review.Verb) == 0 {
28
+	if len(review.Action.Verb) == 0 {
29 29
 		allErrs = append(allErrs, fielderrors.NewFieldRequired("verb"))
30 30
 	}
31
-	if len(review.Resource) == 0 {
31
+	if len(review.Action.Resource) == 0 {
32
+		allErrs = append(allErrs, fielderrors.NewFieldRequired("resource"))
33
+	}
34
+
35
+	return allErrs
36
+}
37
+
38
+func ValidateLocalSubjectAccessReview(review *authorizationapi.LocalSubjectAccessReview) fielderrors.ValidationErrorList {
39
+	allErrs := fielderrors.ValidationErrorList{}
40
+
41
+	if len(review.Action.Verb) == 0 {
42
+		allErrs = append(allErrs, fielderrors.NewFieldRequired("verb"))
43
+	}
44
+	if len(review.Action.Resource) == 0 {
45
+		allErrs = append(allErrs, fielderrors.NewFieldRequired("resource"))
46
+	}
47
+
48
+	return allErrs
49
+}
50
+
51
+func ValidateLocalResourceAccessReview(review *authorizationapi.LocalResourceAccessReview) fielderrors.ValidationErrorList {
52
+	allErrs := fielderrors.ValidationErrorList{}
53
+
54
+	if len(review.Action.Verb) == 0 {
55
+		allErrs = append(allErrs, fielderrors.NewFieldRequired("verb"))
56
+	}
57
+	if len(review.Action.Resource) == 0 {
32 58
 		allErrs = append(allErrs, fielderrors.NewFieldRequired("resource"))
33 59
 	}
34 60
 
... ...
@@ -20,6 +20,16 @@ type DefaultAuthorizationAttributes struct {
20 20
 	URL               string
21 21
 }
22 22
 
23
+// ToDefaultAuthorizationAttributes coerces AuthorizationAttributes to DefaultAuthorizationAttributes.  Namespace is not included
24
+// because the authorizer takes that information on the context
25
+func ToDefaultAuthorizationAttributes(in authorizationapi.AuthorizationAttributes) DefaultAuthorizationAttributes {
26
+	return DefaultAuthorizationAttributes{
27
+		Verb:         in.Verb,
28
+		Resource:     in.Resource,
29
+		ResourceName: in.ResourceName,
30
+	}
31
+}
32
+
23 33
 func (a DefaultAuthorizationAttributes) RuleMatches(rule authorizationapi.PolicyRule) (bool, error) {
24 34
 	if a.IsNonResourceURL() {
25 35
 		if a.nonResourceMatches(rule) {
... ...
@@ -2,7 +2,7 @@ package authorizer
2 2
 
3 3
 import (
4 4
 	"bytes"
5
-	"errors"
5
+	"fmt"
6 6
 	"io/ioutil"
7 7
 	"net/http"
8 8
 
... ...
@@ -11,11 +11,24 @@ import (
11 11
 )
12 12
 
13 13
 func IsPersonalAccessReview(a AuthorizationAttributes) (bool, error) {
14
-	req, ok := a.GetRequestAttributes().(*http.Request)
15
-	if !ok {
16
-		return false, errors.New("expected request, but did not get one")
14
+	switch extendedAttributes := a.GetRequestAttributes().(type) {
15
+	case *http.Request:
16
+		return isPersonalAccessReviewFromRequest(a, extendedAttributes)
17
+
18
+	case *authorizationapi.SubjectAccessReview:
19
+		return isPersonalAccessReviewFromSAR(extendedAttributes), nil
20
+
21
+	case *authorizationapi.LocalSubjectAccessReview:
22
+		return isPersonalAccessReviewFromLocalSAR(extendedAttributes), nil
23
+
24
+	default:
25
+		return false, fmt.Errorf("unexpected request attributes for checking personal access review: %v", extendedAttributes)
26
+
17 27
 	}
28
+}
18 29
 
30
+// isPersonalAccessReviewFromRequest this variant handles the case where we have an httpRequest
31
+func isPersonalAccessReviewFromRequest(a AuthorizationAttributes, req *http.Request) (bool, error) {
19 32
 	// TODO once we're integrated with the api installer, we should have direct access to the deserialized content
20 33
 	// for now, this only happens on subjectaccessreviews with a personal check, pay the double retrieve and decode cost
21 34
 	body, err := ioutil.ReadAll(req.Body)
... ...
@@ -24,14 +37,36 @@ func IsPersonalAccessReview(a AuthorizationAttributes) (bool, error) {
24 24
 	}
25 25
 	req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
26 26
 
27
-	subjectAccessReview := &authorizationapi.SubjectAccessReview{}
28
-	if err := latest.Codec.DecodeInto(body, subjectAccessReview); err != nil {
27
+	obj, err := latest.Codec.Decode(body)
28
+	if err != nil {
29 29
 		return false, err
30 30
 	}
31
+	switch castObj := obj.(type) {
32
+	case *authorizationapi.SubjectAccessReview:
33
+		return isPersonalAccessReviewFromSAR(castObj), nil
34
+
35
+	case *authorizationapi.LocalSubjectAccessReview:
36
+		return isPersonalAccessReviewFromLocalSAR(castObj), nil
37
+
38
+	default:
39
+		return false, nil
40
+	}
41
+}
42
+
43
+// isPersonalAccessReviewFromSAR this variant handles the case where we have an SAR
44
+func isPersonalAccessReviewFromSAR(sar *authorizationapi.SubjectAccessReview) bool {
45
+	if len(sar.User) == 0 && len(sar.Groups) == 0 {
46
+		return true
47
+	}
48
+
49
+	return false
50
+}
31 51
 
32
-	if (len(subjectAccessReview.User) == 0) && (len(subjectAccessReview.Groups) == 0) {
33
-		return true, nil
52
+// isPersonalAccessReviewFromLocalSAR this variant handles the case where we have a local SAR
53
+func isPersonalAccessReviewFromLocalSAR(sar *authorizationapi.LocalSubjectAccessReview) bool {
54
+	if len(sar.User) == 0 && len(sar.Groups) == 0 {
55
+		return true
34 56
 	}
35 57
 
36
-	return false, nil
58
+	return false
37 59
 }
38 60
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package localresourceaccessreview
1
+
2
+import (
3
+	api "github.com/openshift/origin/pkg/authorization/api"
4
+	kapi "k8s.io/kubernetes/pkg/api"
5
+	"k8s.io/kubernetes/pkg/runtime"
6
+)
7
+
8
+type Registry interface {
9
+	CreateLocalResourceAccessReview(ctx kapi.Context, resourceAccessReview *api.LocalResourceAccessReview) (*api.ResourceAccessReviewResponse, error)
10
+}
11
+
12
+type Storage interface {
13
+	Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error)
14
+}
15
+
16
+type storage struct {
17
+	Storage
18
+}
19
+
20
+func NewRegistry(s Storage) Registry {
21
+	return &storage{s}
22
+}
23
+
24
+func (s *storage) CreateLocalResourceAccessReview(ctx kapi.Context, resourceAccessReview *api.LocalResourceAccessReview) (*api.ResourceAccessReviewResponse, error) {
25
+	obj, err := s.Create(ctx, resourceAccessReview)
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+	return obj.(*api.ResourceAccessReviewResponse), nil
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,53 @@
0
+package localresourceaccessreview
1
+
2
+import (
3
+	"fmt"
4
+
5
+	kapi "k8s.io/kubernetes/pkg/api"
6
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
7
+	"k8s.io/kubernetes/pkg/runtime"
8
+	kutilerrors "k8s.io/kubernetes/pkg/util/errors"
9
+	"k8s.io/kubernetes/pkg/util/fielderrors"
10
+
11
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
12
+	authorizationvalidation "github.com/openshift/origin/pkg/authorization/api/validation"
13
+	"github.com/openshift/origin/pkg/authorization/registry/resourceaccessreview"
14
+)
15
+
16
+// REST implements the RESTStorage interface in terms of an Registry.
17
+type REST struct {
18
+	clusterRARRegistry resourceaccessreview.Registry
19
+}
20
+
21
+func NewREST(clusterRARRegistry resourceaccessreview.Registry) *REST {
22
+	return &REST{clusterRARRegistry}
23
+}
24
+
25
+func (r *REST) New() runtime.Object {
26
+	return &authorizationapi.LocalResourceAccessReview{}
27
+}
28
+
29
+// Create transforms a LocalRAR into an ClusterRAR that is requesting a namespace.  That collapses the code paths.
30
+// LocalResourceAccessReview exists to allow clean expression of policy.
31
+func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
32
+	localRAR, ok := obj.(*authorizationapi.LocalResourceAccessReview)
33
+	if !ok {
34
+		return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a localResourceAccessReview: %#v", obj))
35
+	}
36
+	if err := kutilerrors.NewAggregate(authorizationvalidation.ValidateLocalResourceAccessReview(localRAR)); err != nil {
37
+		return nil, err
38
+	}
39
+	if namespace := kapi.NamespaceValue(ctx); len(namespace) == 0 {
40
+		return nil, kapierrors.NewBadRequest(fmt.Sprintf("namespace is required on this type: %v", namespace))
41
+	} else if (len(localRAR.Action.Namespace) > 0) && (namespace != localRAR.Action.Namespace) {
42
+		return nil, fielderrors.NewFieldInvalid("namespace", localRAR.Action.Namespace, fmt.Sprintf("namespace must be: %v", namespace))
43
+	}
44
+
45
+	// transform this into a ResourceAccessReview
46
+	clusterRAR := &authorizationapi.ResourceAccessReview{
47
+		Action: localRAR.Action,
48
+	}
49
+	clusterRAR.Action.Namespace = kapi.NamespaceValue(ctx)
50
+
51
+	return r.clusterRARRegistry.CreateResourceAccessReview(kapi.WithNamespace(ctx, ""), clusterRAR)
52
+}
0 53
new file mode 100644
... ...
@@ -0,0 +1,186 @@
0
+package localresourceaccessreview
1
+
2
+import (
3
+	"errors"
4
+	"reflect"
5
+	"testing"
6
+
7
+	kapi "k8s.io/kubernetes/pkg/api"
8
+	"k8s.io/kubernetes/pkg/util"
9
+
10
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
11
+	"github.com/openshift/origin/pkg/authorization/authorizer"
12
+	"github.com/openshift/origin/pkg/authorization/registry/resourceaccessreview"
13
+)
14
+
15
+type resourceAccessTest struct {
16
+	authorizer    *testAuthorizer
17
+	reviewRequest *authorizationapi.LocalResourceAccessReview
18
+}
19
+
20
+type testAuthorizer struct {
21
+	users  util.StringSet
22
+	groups util.StringSet
23
+	err    string
24
+
25
+	actualAttributes authorizer.DefaultAuthorizationAttributes
26
+}
27
+
28
+func (a *testAuthorizer) Authorize(ctx kapi.Context, attributes authorizer.AuthorizationAttributes) (allowed bool, reason string, err error) {
29
+	// allow the initial check for "can I run this RAR at all"
30
+	if attributes.GetResource() == "localresourceaccessreviews" {
31
+		return true, "", nil
32
+	}
33
+
34
+	return false, "", errors.New("Unsupported")
35
+}
36
+func (a *testAuthorizer) GetAllowedSubjects(ctx kapi.Context, passedAttributes authorizer.AuthorizationAttributes) (util.StringSet, util.StringSet, error) {
37
+	attributes, ok := passedAttributes.(authorizer.DefaultAuthorizationAttributes)
38
+	if !ok {
39
+		return nil, nil, errors.New("unexpected type for test")
40
+	}
41
+
42
+	a.actualAttributes = attributes
43
+	if len(a.err) == 0 {
44
+		return a.users, a.groups, nil
45
+	}
46
+	return a.users, a.groups, errors.New(a.err)
47
+}
48
+
49
+func TestNoNamespace(t *testing.T) {
50
+	test := &resourceAccessTest{
51
+		authorizer: &testAuthorizer{
52
+			err: "namespace is required on this type: ",
53
+		},
54
+		reviewRequest: &authorizationapi.LocalResourceAccessReview{
55
+			Action: authorizationapi.AuthorizationAttributes{
56
+				Namespace: "",
57
+				Verb:      "get",
58
+				Resource:  "pods",
59
+			},
60
+		},
61
+	}
62
+
63
+	test.runTest(t)
64
+}
65
+
66
+func TestConflictingNamespace(t *testing.T) {
67
+	authorizer := &testAuthorizer{}
68
+	reviewRequest := &authorizationapi.LocalResourceAccessReview{
69
+		Action: authorizationapi.AuthorizationAttributes{
70
+			Namespace: "foo",
71
+			Verb:      "get",
72
+			Resource:  "pods",
73
+		},
74
+	}
75
+
76
+	storage := NewREST(resourceaccessreview.NewRegistry(resourceaccessreview.NewREST(authorizer)))
77
+	ctx := kapi.WithNamespace(kapi.NewContext(), "bar")
78
+	_, err := storage.Create(ctx, reviewRequest)
79
+	if err == nil {
80
+		t.Fatalf("unexpected non-error: %v", err)
81
+	}
82
+	if e, a := "namespace: invalid value 'foo', Details: namespace must be: bar", err.Error(); e != a {
83
+		t.Fatalf("expected %v, got %v", e, a)
84
+	}
85
+}
86
+
87
+func TestEmptyReturn(t *testing.T) {
88
+	test := &resourceAccessTest{
89
+		authorizer: &testAuthorizer{
90
+			users:  util.StringSet{},
91
+			groups: util.StringSet{},
92
+		},
93
+		reviewRequest: &authorizationapi.LocalResourceAccessReview{
94
+			Action: authorizationapi.AuthorizationAttributes{
95
+				Namespace: "unittest",
96
+				Verb:      "get",
97
+				Resource:  "pods",
98
+			},
99
+		},
100
+	}
101
+
102
+	test.runTest(t)
103
+}
104
+
105
+func TestNoErrors(t *testing.T) {
106
+	test := &resourceAccessTest{
107
+		authorizer: &testAuthorizer{
108
+			users:  util.NewStringSet("one", "two"),
109
+			groups: util.NewStringSet("three", "four"),
110
+		},
111
+		reviewRequest: &authorizationapi.LocalResourceAccessReview{
112
+			Action: authorizationapi.AuthorizationAttributes{
113
+				Namespace: "unittest",
114
+				Verb:      "delete",
115
+				Resource:  "deploymentConfig",
116
+			},
117
+		},
118
+	}
119
+
120
+	test.runTest(t)
121
+}
122
+
123
+func TestErrors(t *testing.T) {
124
+	test := &resourceAccessTest{
125
+		authorizer: &testAuthorizer{
126
+			users:  util.StringSet{},
127
+			groups: util.StringSet{},
128
+			err:    "some-random-failure",
129
+		},
130
+		reviewRequest: &authorizationapi.LocalResourceAccessReview{
131
+			Action: authorizationapi.AuthorizationAttributes{
132
+				Namespace: "unittest",
133
+				Verb:      "get",
134
+				Resource:  "pods",
135
+			},
136
+		},
137
+	}
138
+
139
+	test.runTest(t)
140
+}
141
+
142
+func (r *resourceAccessTest) runTest(t *testing.T) {
143
+	storage := NewREST(resourceaccessreview.NewRegistry(resourceaccessreview.NewREST(r.authorizer)))
144
+
145
+	expectedResponse := &authorizationapi.ResourceAccessReviewResponse{
146
+		Namespace: r.reviewRequest.Action.Namespace,
147
+		Users:     r.authorizer.users,
148
+		Groups:    r.authorizer.groups,
149
+	}
150
+
151
+	expectedAttributes := authorizer.ToDefaultAuthorizationAttributes(r.reviewRequest.Action)
152
+
153
+	ctx := kapi.WithNamespace(kapi.NewContext(), r.reviewRequest.Action.Namespace)
154
+	obj, err := storage.Create(ctx, r.reviewRequest)
155
+	if err != nil && len(r.authorizer.err) == 0 {
156
+		t.Fatalf("unexpected error: %v", err)
157
+	}
158
+	if len(r.authorizer.err) != 0 {
159
+		if err == nil {
160
+			t.Fatalf("unexpected non-error: %v", err)
161
+		}
162
+		if e, a := r.authorizer.err, err.Error(); e != a {
163
+			t.Fatalf("expected %v, got %v", e, a)
164
+		}
165
+
166
+		return
167
+	}
168
+
169
+	switch obj.(type) {
170
+	case *authorizationapi.ResourceAccessReviewResponse:
171
+		if !reflect.DeepEqual(expectedResponse, obj) {
172
+			t.Errorf("diff %v", util.ObjectGoPrintDiff(expectedResponse, obj))
173
+		}
174
+	case nil:
175
+		if len(r.authorizer.err) == 0 {
176
+			t.Fatal("unexpected nil object")
177
+		}
178
+	default:
179
+		t.Errorf("Unexpected obj type: %v", obj)
180
+	}
181
+
182
+	if !reflect.DeepEqual(expectedAttributes, r.authorizer.actualAttributes) {
183
+		t.Errorf("diff %v", util.ObjectGoPrintDiff(expectedAttributes, r.authorizer.actualAttributes))
184
+	}
185
+}
0 186
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package localsubjectaccessreview
1
+
2
+import (
3
+	api "github.com/openshift/origin/pkg/authorization/api"
4
+	kapi "k8s.io/kubernetes/pkg/api"
5
+	"k8s.io/kubernetes/pkg/runtime"
6
+)
7
+
8
+type Registry interface {
9
+	CreateLocalSubjectAccessReview(ctx kapi.Context, subjectAccessReview *api.LocalSubjectAccessReview) (*api.SubjectAccessReviewResponse, error)
10
+}
11
+
12
+type Storage interface {
13
+	Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error)
14
+}
15
+
16
+type storage struct {
17
+	Storage
18
+}
19
+
20
+func NewRegistry(s Storage) Registry {
21
+	return &storage{s}
22
+}
23
+
24
+func (s *storage) CreateLocalSubjectAccessReview(ctx kapi.Context, subjectAccessReview *api.LocalSubjectAccessReview) (*api.SubjectAccessReviewResponse, error) {
25
+	obj, err := s.Create(ctx, subjectAccessReview)
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+	return obj.(*api.SubjectAccessReviewResponse), nil
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,55 @@
0
+package localsubjectaccessreview
1
+
2
+import (
3
+	"fmt"
4
+
5
+	kapi "k8s.io/kubernetes/pkg/api"
6
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
7
+	"k8s.io/kubernetes/pkg/runtime"
8
+	kutilerrors "k8s.io/kubernetes/pkg/util/errors"
9
+	"k8s.io/kubernetes/pkg/util/fielderrors"
10
+
11
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
12
+	authorizationvalidation "github.com/openshift/origin/pkg/authorization/api/validation"
13
+	"github.com/openshift/origin/pkg/authorization/registry/subjectaccessreview"
14
+)
15
+
16
+// REST implements the RESTStorage interface in terms of an Registry.
17
+type REST struct {
18
+	clusterSARRegistry subjectaccessreview.Registry
19
+}
20
+
21
+func NewREST(clusterSARRegistry subjectaccessreview.Registry) *REST {
22
+	return &REST{clusterSARRegistry}
23
+}
24
+
25
+func (r *REST) New() runtime.Object {
26
+	return &authorizationapi.LocalSubjectAccessReview{}
27
+}
28
+
29
+// Create transforms a LocalSAR into an ClusterSAR that is requesting a namespace.  That collapses the code paths.
30
+// LocalSubjectAccessReview exists to allow clean expression of policy.
31
+func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
32
+	localSAR, ok := obj.(*authorizationapi.LocalSubjectAccessReview)
33
+	if !ok {
34
+		return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a localSubjectAccessReview: %#v", obj))
35
+	}
36
+	if err := kutilerrors.NewAggregate(authorizationvalidation.ValidateLocalSubjectAccessReview(localSAR)); err != nil {
37
+		return nil, err
38
+	}
39
+	if namespace := kapi.NamespaceValue(ctx); len(namespace) == 0 {
40
+		return nil, kapierrors.NewBadRequest(fmt.Sprintf("namespace is required on this type: %v", namespace))
41
+	} else if (len(localSAR.Action.Namespace) > 0) && (namespace != localSAR.Action.Namespace) {
42
+		return nil, fielderrors.NewFieldInvalid("namespace", localSAR.Action.Namespace, fmt.Sprintf("namespace must be: %v", namespace))
43
+	}
44
+
45
+	// transform this into a SubjectAccessReview
46
+	clusterSAR := &authorizationapi.SubjectAccessReview{
47
+		Action: localSAR.Action,
48
+		User:   localSAR.User,
49
+		Groups: localSAR.Groups,
50
+	}
51
+	clusterSAR.Action.Namespace = kapi.NamespaceValue(ctx)
52
+
53
+	return r.clusterSARRegistry.CreateSubjectAccessReview(kapi.WithNamespace(ctx, ""), clusterSAR)
54
+}
0 55
new file mode 100644
... ...
@@ -0,0 +1,198 @@
0
+package localsubjectaccessreview
1
+
2
+import (
3
+	"errors"
4
+	"reflect"
5
+	"testing"
6
+
7
+	kapi "k8s.io/kubernetes/pkg/api"
8
+	"k8s.io/kubernetes/pkg/util"
9
+
10
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
11
+	"github.com/openshift/origin/pkg/authorization/authorizer"
12
+	"github.com/openshift/origin/pkg/authorization/registry/subjectaccessreview"
13
+)
14
+
15
+type subjectAccessTest struct {
16
+	authorizer    *testAuthorizer
17
+	reviewRequest *authorizationapi.LocalSubjectAccessReview
18
+}
19
+
20
+type testAuthorizer struct {
21
+	allowed bool
22
+	reason  string
23
+	err     string
24
+
25
+	actualAttributes authorizer.DefaultAuthorizationAttributes
26
+}
27
+
28
+func (a *testAuthorizer) Authorize(ctx kapi.Context, passedAttributes authorizer.AuthorizationAttributes) (allowed bool, reason string, err error) {
29
+	// allow the initial check for "can I run this SAR at all"
30
+	if passedAttributes.GetResource() == "localsubjectaccessreviews" {
31
+		return true, "", nil
32
+	}
33
+
34
+	attributes, ok := passedAttributes.(authorizer.DefaultAuthorizationAttributes)
35
+	if !ok {
36
+		return false, "ERROR", errors.New("unexpected type for test")
37
+	}
38
+
39
+	a.actualAttributes = attributes
40
+
41
+	if len(a.err) == 0 {
42
+		return a.allowed, a.reason, nil
43
+	}
44
+	return a.allowed, a.reason, errors.New(a.err)
45
+}
46
+func (a *testAuthorizer) GetAllowedSubjects(ctx kapi.Context, passedAttributes authorizer.AuthorizationAttributes) (util.StringSet, util.StringSet, error) {
47
+	return util.StringSet{}, util.StringSet{}, nil
48
+}
49
+
50
+func TestNoNamespace(t *testing.T) {
51
+	test := &subjectAccessTest{
52
+		authorizer: &testAuthorizer{
53
+			allowed: false,
54
+			err:     "namespace is required on this type: ",
55
+		},
56
+		reviewRequest: &authorizationapi.LocalSubjectAccessReview{
57
+			Action: authorizationapi.AuthorizationAttributes{
58
+				Namespace: "",
59
+				Verb:      "get",
60
+				Resource:  "pods",
61
+			},
62
+			User:   "foo",
63
+			Groups: util.NewStringSet(),
64
+		},
65
+	}
66
+
67
+	test.runTest(t)
68
+}
69
+
70
+func TestConflictingNamespace(t *testing.T) {
71
+	authorizer := &testAuthorizer{
72
+		allowed: false,
73
+	}
74
+	reviewRequest := &authorizationapi.LocalSubjectAccessReview{
75
+		Action: authorizationapi.AuthorizationAttributes{
76
+			Namespace: "foo",
77
+			Verb:      "get",
78
+			Resource:  "pods",
79
+		},
80
+		User:   "foo",
81
+		Groups: util.NewStringSet(),
82
+	}
83
+
84
+	storage := NewREST(subjectaccessreview.NewRegistry(subjectaccessreview.NewREST(authorizer)))
85
+	ctx := kapi.WithNamespace(kapi.NewContext(), "bar")
86
+	_, err := storage.Create(ctx, reviewRequest)
87
+	if err == nil {
88
+		t.Fatalf("unexpected non-error: %v", err)
89
+	}
90
+	if e, a := "namespace: invalid value 'foo', Details: namespace must be: bar", err.Error(); e != a {
91
+		t.Fatalf("expected %v, got %v", e, a)
92
+	}
93
+}
94
+
95
+func TestEmptyReturn(t *testing.T) {
96
+	test := &subjectAccessTest{
97
+		authorizer: &testAuthorizer{
98
+			allowed: false,
99
+			reason:  "because reasons",
100
+		},
101
+		reviewRequest: &authorizationapi.LocalSubjectAccessReview{
102
+			Action: authorizationapi.AuthorizationAttributes{
103
+				Namespace: "unittest",
104
+				Verb:      "get",
105
+				Resource:  "pods",
106
+			},
107
+			User:   "foo",
108
+			Groups: util.NewStringSet(),
109
+		},
110
+	}
111
+
112
+	test.runTest(t)
113
+}
114
+
115
+func TestNoErrors(t *testing.T) {
116
+	test := &subjectAccessTest{
117
+		authorizer: &testAuthorizer{
118
+			allowed: true,
119
+			reason:  "because good things",
120
+		},
121
+		reviewRequest: &authorizationapi.LocalSubjectAccessReview{
122
+			Action: authorizationapi.AuthorizationAttributes{
123
+				Namespace: "unittest",
124
+				Verb:      "delete",
125
+				Resource:  "deploymentConfigs",
126
+			},
127
+			Groups: util.NewStringSet("not-master"),
128
+		},
129
+	}
130
+
131
+	test.runTest(t)
132
+}
133
+
134
+func TestErrors(t *testing.T) {
135
+	test := &subjectAccessTest{
136
+		authorizer: &testAuthorizer{
137
+			err: "some-random-failure",
138
+		},
139
+		reviewRequest: &authorizationapi.LocalSubjectAccessReview{
140
+			Action: authorizationapi.AuthorizationAttributes{
141
+				Namespace: "unittest",
142
+				Verb:      "get",
143
+				Resource:  "pods",
144
+			},
145
+			User:   "foo",
146
+			Groups: util.NewStringSet("first", "second"),
147
+		},
148
+	}
149
+
150
+	test.runTest(t)
151
+}
152
+
153
+func (r *subjectAccessTest) runTest(t *testing.T) {
154
+	storage := NewREST(subjectaccessreview.NewRegistry(subjectaccessreview.NewREST(r.authorizer)))
155
+
156
+	expectedResponse := &authorizationapi.SubjectAccessReviewResponse{
157
+		Namespace: r.reviewRequest.Action.Namespace,
158
+		Allowed:   r.authorizer.allowed,
159
+		Reason:    r.authorizer.reason,
160
+	}
161
+
162
+	expectedAttributes := authorizer.ToDefaultAuthorizationAttributes(r.reviewRequest.Action)
163
+
164
+	ctx := kapi.WithNamespace(kapi.NewContext(), r.reviewRequest.Action.Namespace)
165
+	obj, err := storage.Create(ctx, r.reviewRequest)
166
+	if err != nil && len(r.authorizer.err) == 0 {
167
+		t.Fatalf("unexpected error: %v", err)
168
+	}
169
+	if len(r.authorizer.err) != 0 {
170
+		if err == nil {
171
+			t.Fatalf("unexpected non-error: %v", err)
172
+		}
173
+		if e, a := r.authorizer.err, err.Error(); e != a {
174
+			t.Fatalf("expected %v, got %v", e, a)
175
+		}
176
+
177
+		return
178
+	}
179
+
180
+	switch obj.(type) {
181
+	case *authorizationapi.SubjectAccessReviewResponse:
182
+		if !reflect.DeepEqual(expectedResponse, obj) {
183
+			t.Errorf("diff %v", util.ObjectGoPrintDiff(expectedResponse, obj))
184
+		}
185
+	case nil:
186
+		if len(r.authorizer.err) == 0 {
187
+			t.Fatal("unexpected nil object")
188
+		}
189
+
190
+	default:
191
+		t.Errorf("Unexpected obj type: %v", obj)
192
+	}
193
+
194
+	if !reflect.DeepEqual(expectedAttributes, r.authorizer.actualAttributes) {
195
+		t.Errorf("diff %v", util.ObjectGoPrintDiff(expectedAttributes, r.authorizer.actualAttributes))
196
+	}
197
+}
0 198
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package resourceaccessreview
1
+
2
+import (
3
+	api "github.com/openshift/origin/pkg/authorization/api"
4
+	kapi "k8s.io/kubernetes/pkg/api"
5
+	"k8s.io/kubernetes/pkg/runtime"
6
+)
7
+
8
+type Registry interface {
9
+	CreateResourceAccessReview(ctx kapi.Context, resourceAccessReview *api.ResourceAccessReview) (*api.ResourceAccessReviewResponse, error)
10
+}
11
+
12
+type Storage interface {
13
+	Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error)
14
+}
15
+
16
+type storage struct {
17
+	Storage
18
+}
19
+
20
+func NewRegistry(s Storage) Registry {
21
+	return &storage{s}
22
+}
23
+
24
+func (s *storage) CreateResourceAccessReview(ctx kapi.Context, resourceAccessReview *api.ResourceAccessReview) (*api.ResourceAccessReviewResponse, error) {
25
+	obj, err := s.Create(ctx, resourceAccessReview)
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+	return obj.(*api.ResourceAccessReviewResponse), nil
30
+}
... ...
@@ -1,10 +1,11 @@
1 1
 package resourceaccessreview
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"fmt"
5 6
 
6 7
 	kapi "k8s.io/kubernetes/pkg/api"
7
-	"k8s.io/kubernetes/pkg/api/errors"
8
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
8 9
 	"k8s.io/kubernetes/pkg/runtime"
9 10
 	kutilerrors "k8s.io/kubernetes/pkg/util/errors"
10 11
 
... ...
@@ -32,29 +33,53 @@ func (r *REST) New() runtime.Object {
32 32
 func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
33 33
 	resourceAccessReview, ok := obj.(*authorizationapi.ResourceAccessReview)
34 34
 	if !ok {
35
-		return nil, errors.NewBadRequest(fmt.Sprintf("not a resourceAccessReview: %#v", obj))
35
+		return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a resourceAccessReview: %#v", obj))
36 36
 	}
37 37
 	if err := kutilerrors.NewAggregate(authorizationvalidation.ValidateResourceAccessReview(resourceAccessReview)); err != nil {
38 38
 		return nil, err
39 39
 	}
40
-
41
-	namespace := kapi.NamespaceValue(ctx)
42
-
43
-	attributes := &authorizer.DefaultAuthorizationAttributes{
44
-		Verb:     resourceAccessReview.Verb,
45
-		Resource: resourceAccessReview.Resource,
40
+	// if a namespace is present on the request, then the namespace on the on the RAR is overwritten.
41
+	// This is to support backwards compatibility.  To have gotten here in this state, it means that
42
+	// the authorizer decided that a user could run an RAR against this namespace
43
+	if namespace := kapi.NamespaceValue(ctx); len(namespace) > 0 {
44
+		resourceAccessReview.Action.Namespace = namespace
45
+	}
46
+	if err := r.isAllowed(ctx, resourceAccessReview); err != nil {
47
+		return nil, err
46 48
 	}
47 49
 
48
-	users, groups, err := r.authorizer.GetAllowedSubjects(ctx, attributes)
50
+	requestContext := kapi.WithNamespace(ctx, resourceAccessReview.Action.Namespace)
51
+	attributes := authorizer.ToDefaultAuthorizationAttributes(resourceAccessReview.Action)
52
+	users, groups, err := r.authorizer.GetAllowedSubjects(requestContext, attributes)
49 53
 	if err != nil {
50 54
 		return nil, err
51 55
 	}
52 56
 
53 57
 	response := &authorizationapi.ResourceAccessReviewResponse{
54
-		Namespace: namespace,
58
+		Namespace: resourceAccessReview.Action.Namespace,
55 59
 		Users:     users,
56 60
 		Groups:    groups,
57 61
 	}
58 62
 
59 63
 	return response, nil
60 64
 }
65
+
66
+// isAllowed checks to see if the current user has rights to issue a LocalSubjectAccessReview on the namespace they're attempting to access
67
+func (r *REST) isAllowed(ctx kapi.Context, rar *authorizationapi.ResourceAccessReview) error {
68
+	localRARAttributes := authorizer.DefaultAuthorizationAttributes{
69
+		Verb:     "create",
70
+		Resource: "localresourceaccessreviews",
71
+	}
72
+	allowed, reason, err := r.authorizer.Authorize(kapi.WithNamespace(ctx, rar.Action.Namespace), localRARAttributes)
73
+
74
+	if err != nil {
75
+		return kapierrors.NewForbidden(localRARAttributes.GetResource(), localRARAttributes.GetResourceName(), err)
76
+	}
77
+	if !allowed {
78
+		forbiddenError, _ := kapierrors.NewForbidden(localRARAttributes.GetResource(), localRARAttributes.GetResourceName(), errors.New("") /*discarded*/).(*kapierrors.StatusError)
79
+		forbiddenError.ErrStatus.Message = reason
80
+		return forbiddenError
81
+	}
82
+
83
+	return nil
84
+}
... ...
@@ -18,18 +18,28 @@ type resourceAccessTest struct {
18 18
 }
19 19
 
20 20
 type testAuthorizer struct {
21
-	users  util.StringSet
22
-	groups util.StringSet
23
-	err    string
21
+	users            util.StringSet
22
+	groups           util.StringSet
23
+	err              string
24
+	deniedNamespaces util.StringSet
24 25
 
25
-	actualAttributes *authorizer.DefaultAuthorizationAttributes
26
+	actualAttributes authorizer.DefaultAuthorizationAttributes
26 27
 }
27 28
 
28 29
 func (a *testAuthorizer) Authorize(ctx kapi.Context, attributes authorizer.AuthorizationAttributes) (allowed bool, reason string, err error) {
30
+	// allow the initial check for "can I run this RAR at all"
31
+	if attributes.GetResource() == "localresourceaccessreviews" {
32
+		if len(a.deniedNamespaces) != 0 && a.deniedNamespaces.Has(kapi.NamespaceValue(ctx)) {
33
+			return false, "denied initial check", nil
34
+		}
35
+
36
+		return true, "", nil
37
+	}
38
+
29 39
 	return false, "", errors.New("unsupported")
30 40
 }
31 41
 func (a *testAuthorizer) GetAllowedSubjects(ctx kapi.Context, passedAttributes authorizer.AuthorizationAttributes) (util.StringSet, util.StringSet, error) {
32
-	attributes, ok := passedAttributes.(*authorizer.DefaultAuthorizationAttributes)
42
+	attributes, ok := passedAttributes.(authorizer.DefaultAuthorizationAttributes)
33 43
 	if !ok {
34 44
 		return nil, nil, errors.New("unexpected type for test")
35 45
 	}
... ...
@@ -41,6 +51,26 @@ func (a *testAuthorizer) GetAllowedSubjects(ctx kapi.Context, passedAttributes a
41 41
 	return a.users, a.groups, errors.New(a.err)
42 42
 }
43 43
 
44
+func TestDeniedNamespace(t *testing.T) {
45
+	test := &resourceAccessTest{
46
+		authorizer: &testAuthorizer{
47
+			users:            util.StringSet{},
48
+			groups:           util.StringSet{},
49
+			err:              "denied initial check",
50
+			deniedNamespaces: util.NewStringSet("foo"),
51
+		},
52
+		reviewRequest: &authorizationapi.ResourceAccessReview{
53
+			Action: authorizationapi.AuthorizationAttributes{
54
+				Namespace: "foo",
55
+				Verb:      "get",
56
+				Resource:  "pods",
57
+			},
58
+		},
59
+	}
60
+
61
+	test.runTest(t)
62
+}
63
+
44 64
 func TestEmptyReturn(t *testing.T) {
45 65
 	test := &resourceAccessTest{
46 66
 		authorizer: &testAuthorizer{
... ...
@@ -48,8 +78,10 @@ func TestEmptyReturn(t *testing.T) {
48 48
 			groups: util.StringSet{},
49 49
 		},
50 50
 		reviewRequest: &authorizationapi.ResourceAccessReview{
51
-			Verb:     "get",
52
-			Resource: "pods",
51
+			Action: authorizationapi.AuthorizationAttributes{
52
+				Verb:     "get",
53
+				Resource: "pods",
54
+			},
53 55
 		},
54 56
 	}
55 57
 
... ...
@@ -63,8 +95,10 @@ func TestNoErrors(t *testing.T) {
63 63
 			groups: util.NewStringSet("three", "four"),
64 64
 		},
65 65
 		reviewRequest: &authorizationapi.ResourceAccessReview{
66
-			Verb:     "delete",
67
-			Resource: "deploymentConfig",
66
+			Action: authorizationapi.AuthorizationAttributes{
67
+				Verb:     "delete",
68
+				Resource: "deploymentConfig",
69
+			},
68 70
 		},
69 71
 	}
70 72
 
... ...
@@ -79,8 +113,10 @@ func TestErrors(t *testing.T) {
79 79
 			err:    "some-random-failure",
80 80
 		},
81 81
 		reviewRequest: &authorizationapi.ResourceAccessReview{
82
-			Verb:     "get",
83
-			Resource: "pods",
82
+			Action: authorizationapi.AuthorizationAttributes{
83
+				Verb:     "get",
84
+				Resource: "pods",
85
+			},
84 86
 		},
85 87
 	}
86 88
 
... ...
@@ -88,28 +124,30 @@ func TestErrors(t *testing.T) {
88 88
 }
89 89
 
90 90
 func (r *resourceAccessTest) runTest(t *testing.T) {
91
-	const namespace = "unittest"
92
-
93 91
 	storage := REST{r.authorizer}
94 92
 
95 93
 	expectedResponse := &authorizationapi.ResourceAccessReviewResponse{
96
-		Namespace: namespace,
94
+		Namespace: r.reviewRequest.Action.Namespace,
97 95
 		Users:     r.authorizer.users,
98 96
 		Groups:    r.authorizer.groups,
99 97
 	}
100 98
 
101
-	expectedAttributes := &authorizer.DefaultAuthorizationAttributes{
102
-		Verb:     r.reviewRequest.Verb,
103
-		Resource: r.reviewRequest.Resource,
104
-	}
99
+	expectedAttributes := authorizer.ToDefaultAuthorizationAttributes(r.reviewRequest.Action)
105 100
 
106
-	ctx := kapi.WithNamespace(kapi.NewContext(), namespace)
101
+	ctx := kapi.WithNamespace(kapi.NewContext(), kapi.NamespaceAll)
107 102
 	obj, err := storage.Create(ctx, r.reviewRequest)
108 103
 	if err != nil && len(r.authorizer.err) == 0 {
109 104
 		t.Fatalf("unexpected error: %v", err)
110 105
 	}
111
-	if err == nil && len(r.authorizer.err) != 0 {
112
-		t.Fatalf("unexpected non-error: %v", err)
106
+	if len(r.authorizer.err) != 0 {
107
+		if err == nil {
108
+			t.Fatalf("unexpected non-error: %v", err)
109
+		}
110
+		if e, a := r.authorizer.err, err.Error(); e != a {
111
+			t.Fatalf("expected %v, got %v", e, a)
112
+		}
113
+
114
+		return
113 115
 	}
114 116
 
115 117
 	switch obj.(type) {
... ...
@@ -1,10 +1,11 @@
1 1
 package subjectaccessreview
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"fmt"
5 6
 
6 7
 	kapi "k8s.io/kubernetes/pkg/api"
7
-	kerrors "k8s.io/kubernetes/pkg/api/errors"
8
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
8 9
 	"k8s.io/kubernetes/pkg/auth/user"
9 10
 	"k8s.io/kubernetes/pkg/runtime"
10 11
 	kutilerrors "k8s.io/kubernetes/pkg/util/errors"
... ...
@@ -33,18 +34,27 @@ func (r *REST) New() runtime.Object {
33 33
 func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
34 34
 	subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview)
35 35
 	if !ok {
36
-		return nil, kerrors.NewBadRequest(fmt.Sprintf("not a subjectAccessReview: %#v", obj))
36
+		return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a subjectAccessReview: %#v", obj))
37 37
 	}
38 38
 	if err := kutilerrors.NewAggregate(authorizationvalidation.ValidateSubjectAccessReview(subjectAccessReview)); err != nil {
39 39
 		return nil, err
40 40
 	}
41
+	// if a namespace is present on the request, then the namespace on the on the SAR is overwritten.
42
+	// This is to support backwards compatibility.  To have gotten here in this state, it means that
43
+	// the authorizer decided that a user could run an SAR against this namespace
44
+	if namespace := kapi.NamespaceValue(ctx); len(namespace) > 0 {
45
+		subjectAccessReview.Action.Namespace = namespace
46
+	}
47
+	if err := r.isAllowed(ctx, subjectAccessReview); err != nil {
48
+		return nil, err
49
+	}
41 50
 
42 51
 	var userToCheck user.Info
43 52
 	if (len(subjectAccessReview.User) == 0) && (len(subjectAccessReview.Groups) == 0) {
44 53
 		// if no user or group was specified, use the info from the context
45 54
 		ctxUser, exists := kapi.UserFrom(ctx)
46 55
 		if !exists {
47
-			return nil, kerrors.NewBadRequest("user missing from context")
56
+			return nil, kapierrors.NewBadRequest("user missing from context")
48 57
 		}
49 58
 		userToCheck = ctxUser
50 59
 
... ...
@@ -56,24 +66,39 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err
56 56
 
57 57
 	}
58 58
 
59
-	namespace := kapi.NamespaceValue(ctx)
60
-	requestContext := kapi.WithUser(ctx, userToCheck)
61
-
62
-	attributes := &authorizer.DefaultAuthorizationAttributes{
63
-		Verb:     subjectAccessReview.Verb,
64
-		Resource: subjectAccessReview.Resource,
65
-	}
66
-
59
+	requestContext := kapi.WithNamespace(kapi.WithUser(ctx, userToCheck), subjectAccessReview.Action.Namespace)
60
+	attributes := authorizer.ToDefaultAuthorizationAttributes(subjectAccessReview.Action)
67 61
 	allowed, reason, err := r.authorizer.Authorize(requestContext, attributes)
68 62
 	if err != nil {
69 63
 		return nil, err
70 64
 	}
71 65
 
72 66
 	response := &authorizationapi.SubjectAccessReviewResponse{
73
-		Namespace: namespace,
67
+		Namespace: subjectAccessReview.Action.Namespace,
74 68
 		Allowed:   allowed,
75 69
 		Reason:    reason,
76 70
 	}
77 71
 
78 72
 	return response, nil
79 73
 }
74
+
75
+// isAllowed checks to see if the current user has rights to issue a LocalSubjectAccessReview on the namespace they're attempting to access
76
+func (r *REST) isAllowed(ctx kapi.Context, sar *authorizationapi.SubjectAccessReview) error {
77
+	localSARAttributes := authorizer.DefaultAuthorizationAttributes{
78
+		Verb:              "create",
79
+		Resource:          "localsubjectaccessreviews",
80
+		RequestAttributes: sar,
81
+	}
82
+	allowed, reason, err := r.authorizer.Authorize(kapi.WithNamespace(ctx, sar.Action.Namespace), localSARAttributes)
83
+
84
+	if err != nil {
85
+		return kapierrors.NewForbidden(localSARAttributes.GetResource(), localSARAttributes.GetResourceName(), err)
86
+	}
87
+	if !allowed {
88
+		forbiddenError, _ := kapierrors.NewForbidden(localSARAttributes.GetResource(), localSARAttributes.GetResourceName(), errors.New("") /*discarded*/).(*kapierrors.StatusError)
89
+		forbiddenError.ErrStatus.Message = reason
90
+		return forbiddenError
91
+	}
92
+
93
+	return nil
94
+}
... ...
@@ -18,15 +18,25 @@ type subjectAccessTest struct {
18 18
 }
19 19
 
20 20
 type testAuthorizer struct {
21
-	allowed bool
22
-	reason  string
23
-	err     string
21
+	allowed          bool
22
+	reason           string
23
+	err              string
24
+	deniedNamespaces util.StringSet
24 25
 
25
-	actualAttributes *authorizer.DefaultAuthorizationAttributes
26
+	actualAttributes authorizer.DefaultAuthorizationAttributes
26 27
 }
27 28
 
28 29
 func (a *testAuthorizer) Authorize(ctx kapi.Context, passedAttributes authorizer.AuthorizationAttributes) (allowed bool, reason string, err error) {
29
-	attributes, ok := passedAttributes.(*authorizer.DefaultAuthorizationAttributes)
30
+	// allow the initial check for "can I run this SAR at all"
31
+	if passedAttributes.GetResource() == "localsubjectaccessreviews" {
32
+		if len(a.deniedNamespaces) != 0 && a.deniedNamespaces.Has(kapi.NamespaceValue(ctx)) {
33
+			return false, "denied initial check", nil
34
+		}
35
+
36
+		return true, "", nil
37
+	}
38
+
39
+	attributes, ok := passedAttributes.(authorizer.DefaultAuthorizationAttributes)
30 40
 	if !ok {
31 41
 		return false, "ERROR", errors.New("unexpected type for test")
32 42
 	}
... ...
@@ -42,6 +52,27 @@ func (a *testAuthorizer) GetAllowedSubjects(ctx kapi.Context, passedAttributes a
42 42
 	return util.StringSet{}, util.StringSet{}, nil
43 43
 }
44 44
 
45
+func TestDeniedNamespace(t *testing.T) {
46
+	test := &subjectAccessTest{
47
+		authorizer: &testAuthorizer{
48
+			allowed:          false,
49
+			err:              "denied initial check",
50
+			deniedNamespaces: util.NewStringSet("foo"),
51
+		},
52
+		reviewRequest: &authorizationapi.SubjectAccessReview{
53
+			Action: authorizationapi.AuthorizationAttributes{
54
+				Namespace: "foo",
55
+				Verb:      "get",
56
+				Resource:  "pods",
57
+			},
58
+			User:   "foo",
59
+			Groups: util.NewStringSet(),
60
+		},
61
+	}
62
+
63
+	test.runTest(t)
64
+}
65
+
45 66
 func TestEmptyReturn(t *testing.T) {
46 67
 	test := &subjectAccessTest{
47 68
 		authorizer: &testAuthorizer{
... ...
@@ -49,10 +80,12 @@ func TestEmptyReturn(t *testing.T) {
49 49
 			reason:  "because reasons",
50 50
 		},
51 51
 		reviewRequest: &authorizationapi.SubjectAccessReview{
52
-			User:     "foo",
53
-			Groups:   util.NewStringSet(),
54
-			Verb:     "get",
55
-			Resource: "pods",
52
+			Action: authorizationapi.AuthorizationAttributes{
53
+				Verb:     "get",
54
+				Resource: "pods",
55
+			},
56
+			User:   "foo",
57
+			Groups: util.NewStringSet(),
56 58
 		},
57 59
 	}
58 60
 
... ...
@@ -66,9 +99,11 @@ func TestNoErrors(t *testing.T) {
66 66
 			reason:  "because good things",
67 67
 		},
68 68
 		reviewRequest: &authorizationapi.SubjectAccessReview{
69
-			Groups:   util.NewStringSet("not-master"),
70
-			Verb:     "delete",
71
-			Resource: "deploymentConfigs",
69
+			Action: authorizationapi.AuthorizationAttributes{
70
+				Verb:     "delete",
71
+				Resource: "deploymentConfigs",
72
+			},
73
+			Groups: util.NewStringSet("not-master"),
72 74
 		},
73 75
 	}
74 76
 
... ...
@@ -81,10 +116,12 @@ func TestErrors(t *testing.T) {
81 81
 			err: "some-random-failure",
82 82
 		},
83 83
 		reviewRequest: &authorizationapi.SubjectAccessReview{
84
-			User:     "foo",
85
-			Groups:   util.NewStringSet("first", "second"),
86
-			Verb:     "get",
87
-			Resource: "pods",
84
+			Action: authorizationapi.AuthorizationAttributes{
85
+				Verb:     "get",
86
+				Resource: "pods",
87
+			},
88
+			User:   "foo",
89
+			Groups: util.NewStringSet("first", "second"),
88 90
 		},
89 91
 	}
90 92
 
... ...
@@ -92,28 +129,30 @@ func TestErrors(t *testing.T) {
92 92
 }
93 93
 
94 94
 func (r *subjectAccessTest) runTest(t *testing.T) {
95
-	const namespace = "unittest"
96
-
97 95
 	storage := REST{r.authorizer}
98 96
 
99 97
 	expectedResponse := &authorizationapi.SubjectAccessReviewResponse{
100
-		Namespace: namespace,
98
+		Namespace: r.reviewRequest.Action.Namespace,
101 99
 		Allowed:   r.authorizer.allowed,
102 100
 		Reason:    r.authorizer.reason,
103 101
 	}
104 102
 
105
-	expectedAttributes := &authorizer.DefaultAuthorizationAttributes{
106
-		Verb:     r.reviewRequest.Verb,
107
-		Resource: r.reviewRequest.Resource,
108
-	}
103
+	expectedAttributes := authorizer.ToDefaultAuthorizationAttributes(r.reviewRequest.Action)
109 104
 
110
-	ctx := kapi.WithNamespace(kapi.NewContext(), namespace)
105
+	ctx := kapi.WithNamespace(kapi.NewContext(), kapi.NamespaceAll)
111 106
 	obj, err := storage.Create(ctx, r.reviewRequest)
112 107
 	if err != nil && len(r.authorizer.err) == 0 {
113 108
 		t.Fatalf("unexpected error: %v", err)
114 109
 	}
115
-	if err == nil && len(r.authorizer.err) != 0 {
116
-		t.Fatalf("unexpected non-error: %v", err)
110
+	if len(r.authorizer.err) != 0 {
111
+		if err == nil {
112
+			t.Fatalf("unexpected non-error: %v", err)
113
+		}
114
+		if e, a := r.authorizer.err, err.Error(); e != a {
115
+			t.Fatalf("expected %v, got %v", e, a)
116
+		}
117
+
118
+		return
117 119
 	}
118 120
 
119 121
 	switch obj.(type) {
... ...
@@ -84,26 +84,30 @@ func resourceName(objectMeta kapi.ObjectMeta) string {
84 84
 
85 85
 func (a *buildByStrategy) checkBuildAuthorization(build *buildapi.Build, attr admission.Attributes) error {
86 86
 	strategyType := build.Spec.Strategy.Type
87
-	subjectAccessReview := &authorizationapi.SubjectAccessReview{
88
-		Verb:         "create",
89
-		Resource:     resourceForStrategyType(strategyType),
90
-		User:         attr.GetUserInfo().GetName(),
91
-		Groups:       util.NewStringSet(attr.GetUserInfo().GetGroups()...),
92
-		Content:      runtime.EmbeddedObject{Object: build},
93
-		ResourceName: resourceName(build.ObjectMeta),
87
+	subjectAccessReview := &authorizationapi.LocalSubjectAccessReview{
88
+		Action: authorizationapi.AuthorizationAttributes{
89
+			Verb:         "create",
90
+			Resource:     resourceForStrategyType(strategyType),
91
+			Content:      runtime.EmbeddedObject{Object: build},
92
+			ResourceName: resourceName(build.ObjectMeta),
93
+		},
94
+		User:   attr.GetUserInfo().GetName(),
95
+		Groups: util.NewStringSet(attr.GetUserInfo().GetGroups()...),
94 96
 	}
95 97
 	return a.checkAccess(strategyType, subjectAccessReview, attr)
96 98
 }
97 99
 
98 100
 func (a *buildByStrategy) checkBuildConfigAuthorization(buildConfig *buildapi.BuildConfig, attr admission.Attributes) error {
99 101
 	strategyType := buildConfig.Spec.Strategy.Type
100
-	subjectAccessReview := &authorizationapi.SubjectAccessReview{
101
-		Verb:         "create",
102
-		Resource:     resourceForStrategyType(strategyType),
103
-		User:         attr.GetUserInfo().GetName(),
104
-		Groups:       util.NewStringSet(attr.GetUserInfo().GetGroups()...),
105
-		Content:      runtime.EmbeddedObject{Object: buildConfig},
106
-		ResourceName: resourceName(buildConfig.ObjectMeta),
102
+	subjectAccessReview := &authorizationapi.LocalSubjectAccessReview{
103
+		Action: authorizationapi.AuthorizationAttributes{
104
+			Verb:         "create",
105
+			Resource:     resourceForStrategyType(strategyType),
106
+			Content:      runtime.EmbeddedObject{Object: buildConfig},
107
+			ResourceName: resourceName(buildConfig.ObjectMeta),
108
+		},
109
+		User:   attr.GetUserInfo().GetName(),
110
+		Groups: util.NewStringSet(attr.GetUserInfo().GetGroups()...),
107 111
 	}
108 112
 	return a.checkAccess(strategyType, subjectAccessReview, attr)
109 113
 }
... ...
@@ -127,8 +131,8 @@ func (a *buildByStrategy) checkBuildRequestAuthorization(req *buildapi.BuildRequ
127 127
 	}
128 128
 }
129 129
 
130
-func (a *buildByStrategy) checkAccess(strategyType buildapi.BuildStrategyType, subjectAccessReview *authorizationapi.SubjectAccessReview, attr admission.Attributes) error {
131
-	resp, err := a.client.SubjectAccessReviews(attr.GetNamespace()).Create(subjectAccessReview)
130
+func (a *buildByStrategy) checkAccess(strategyType buildapi.BuildStrategyType, subjectAccessReview *authorizationapi.LocalSubjectAccessReview, attr admission.Attributes) error {
131
+	resp, err := a.client.LocalSubjectAccessReviews(attr.GetNamespace()).Create(subjectAccessReview)
132 132
 	if err != nil {
133 133
 		return err
134 134
 	}
... ...
@@ -156,14 +156,14 @@ func fakeClient(expectedResource string, reviewResponse *authorizationapi.Subjec
156 156
 	return &testclient.Fake{
157 157
 		ReactFn: func(action ktestclient.Action) (runtime.Object, error) {
158 158
 			switch {
159
-			case action.Matches("create", "subjectaccessreviews"):
160
-				review, ok := action.(ktestclient.CreateAction).GetObject().(*authorizationapi.SubjectAccessReview)
159
+			case action.Matches("create", "localsubjectaccessreviews"):
160
+				review, ok := action.(ktestclient.CreateAction).GetObject().(*authorizationapi.LocalSubjectAccessReview)
161 161
 				if !ok {
162 162
 					return emptyResponse, fmt.Errorf("unexpected object received: %#v", review)
163 163
 				}
164
-				if review.Resource != expectedResource {
164
+				if review.Action.Resource != expectedResource {
165 165
 					return emptyResponse, fmt.Errorf("unexpected resource received: %s. expected: %s",
166
-						review.Resource, expectedResource)
166
+						review.Action.Resource, expectedResource)
167 167
 				}
168 168
 				return reviewResponse, nil
169 169
 			case action.Matches("get", "buildconfigs"):
... ...
@@ -113,7 +113,7 @@ func validateBuildSpec(spec *buildapi.BuildSpec) fielderrors.ValidationErrorList
113 113
 	allErrs = append(allErrs, validateOutput(&spec.Output).Prefix("output")...)
114 114
 	allErrs = append(allErrs, validateStrategy(&spec.Strategy).Prefix("strategy")...)
115 115
 
116
-	// TODO: validate resource requirements (prereq: https://github.com/GoogleCloudPlatform/kubernetes/pull/7059)
116
+	// TODO: validate resource requirements (prereq: https://k8s.io/kubernetes/pull/7059)
117 117
 	return allErrs
118 118
 }
119 119
 
... ...
@@ -34,11 +34,12 @@ type Interface interface {
34 34
 	UserIdentityMappingsInterface
35 35
 	ProjectsInterface
36 36
 	ProjectRequestsInterface
37
-	ResourceAccessReviewsNamespacer
38
-	ClusterResourceAccessReviews
39
-	SubjectAccessReviewsNamespacer
37
+	LocalSubjectAccessReviewsImpersonator
40 38
 	SubjectAccessReviewsImpersonator
41
-	ClusterSubjectAccessReviews
39
+	LocalResourceAccessReviewsNamespacer
40
+	ResourceAccessReviews
41
+	SubjectAccessReviews
42
+	LocalSubjectAccessReviewsNamespacer
42 43
 	TemplatesNamespacer
43 44
 	TemplateConfigsNamespacer
44 45
 	OAuthAccessTokensInterface
... ...
@@ -177,29 +178,34 @@ func (c *Client) RoleBindings(namespace string) RoleBindingInterface {
177 177
 	return newRoleBindings(c, namespace)
178 178
 }
179 179
 
180
-// ResourceAccessReviews provides a REST client for ResourceAccessReviews
181
-func (c *Client) ResourceAccessReviews(namespace string) ResourceAccessReviewInterface {
182
-	return newResourceAccessReviews(c, namespace)
180
+// LocalResourceAccessReviews provides a REST client for LocalResourceAccessReviews
181
+func (c *Client) LocalResourceAccessReviews(namespace string) LocalResourceAccessReviewInterface {
182
+	return newLocalResourceAccessReviews(c, namespace)
183 183
 }
184 184
 
185 185
 // ClusterResourceAccessReviews provides a REST client for ClusterResourceAccessReviews
186
-func (c *Client) ClusterResourceAccessReviews() ResourceAccessReviewInterface {
187
-	return newClusterResourceAccessReviews(c)
186
+func (c *Client) ResourceAccessReviews() ResourceAccessReviewInterface {
187
+	return newResourceAccessReviews(c)
188 188
 }
189 189
 
190
-// SubjectAccessReviews provides a REST client for SubjectAccessReviews
191
-func (c *Client) SubjectAccessReviews(namespace string) SubjectAccessReviewInterface {
192
-	return newSubjectAccessReviews(c, namespace, "")
190
+// ImpersonateSubjectAccessReviews provides a REST client for SubjectAccessReviews
191
+func (c *Client) ImpersonateSubjectAccessReviews(token string) SubjectAccessReviewInterface {
192
+	return newImpersonatingSubjectAccessReviews(c, token)
193 193
 }
194 194
 
195
-// ImpersonateSubjectAccessReviews provides a REST client for SubjectAccessReviews
196
-func (c *Client) ImpersonateSubjectAccessReviews(namespace, token string) SubjectAccessReviewInterface {
197
-	return newSubjectAccessReviews(c, namespace, token)
195
+// ImpersonateLocalSubjectAccessReviews provides a REST client for SubjectAccessReviews
196
+func (c *Client) ImpersonateLocalSubjectAccessReviews(namespace, token string) LocalSubjectAccessReviewInterface {
197
+	return newImpersonatingLocalSubjectAccessReviews(c, namespace, token)
198 198
 }
199 199
 
200
-// ClusterSubjectAccessReviews provides a REST client for SubjectAccessReviews
201
-func (c *Client) ClusterSubjectAccessReviews() SubjectAccessReviewInterface {
202
-	return newClusterSubjectAccessReviews(c)
200
+// LocalSubjectAccessReviews provides a REST client for LocalSubjectAccessReviews
201
+func (c *Client) LocalSubjectAccessReviews(namespace string) LocalSubjectAccessReviewInterface {
202
+	return newLocalSubjectAccessReviews(c, namespace)
203
+}
204
+
205
+// SubjectAccessReviews provides a REST client for SubjectAccessReviews
206
+func (c *Client) SubjectAccessReviews() SubjectAccessReviewInterface {
207
+	return newSubjectAccessReviews(c)
203 208
 }
204 209
 
205 210
 // OAuthAccessTokens provides a REST client for OAuthAccessTokens
206 211
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+package client
1
+
2
+import (
3
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
4
+
5
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
6
+)
7
+
8
+// LocalResourceAccessReviewsNamespacer has methods to work with LocalResourceAccessReview resources in a namespace
9
+type LocalResourceAccessReviewsNamespacer interface {
10
+	LocalResourceAccessReviews(namespace string) LocalResourceAccessReviewInterface
11
+}
12
+
13
+// LocalResourceAccessReviewInterface exposes methods on LocalResourceAccessReview resources.
14
+type LocalResourceAccessReviewInterface interface {
15
+	Create(policy *authorizationapi.LocalResourceAccessReview) (*authorizationapi.ResourceAccessReviewResponse, error)
16
+}
17
+
18
+// localResourceAccessReviews implements ResourceAccessReviewsNamespacer interface
19
+type localResourceAccessReviews struct {
20
+	r  *Client
21
+	ns string
22
+}
23
+
24
+// newLocalResourceAccessReviews returns a localLocalResourceAccessReviews
25
+func newLocalResourceAccessReviews(c *Client, namespace string) *localResourceAccessReviews {
26
+	return &localResourceAccessReviews{
27
+		r:  c,
28
+		ns: namespace,
29
+	}
30
+}
31
+
32
+func (c *localResourceAccessReviews) Create(rar *authorizationapi.LocalResourceAccessReview) (result *authorizationapi.ResourceAccessReviewResponse, err error) {
33
+	result = &authorizationapi.ResourceAccessReviewResponse{}
34
+	err = c.r.Post().Namespace(c.ns).Resource("localResourceAccessReviews").Body(rar).Do().Into(result)
35
+
36
+	// if we get one of these failures, we may be talking to an older openshift.  In that case, we need to try hitting ns/namespace-name/subjectaccessreview
37
+	if kapierrors.IsForbidden(err) || kapierrors.IsNotFound(err) {
38
+		deprecatedRAR := &authorizationapi.ResourceAccessReview{
39
+			Action: rar.Action,
40
+		}
41
+		deprecatedResponse := &authorizationapi.ResourceAccessReviewResponse{}
42
+		deprecatedAttemptErr := c.r.Post().Namespace(c.ns).Resource("resourceAccessReviews").Body(deprecatedRAR).Do().Into(deprecatedResponse)
43
+		if deprecatedAttemptErr == nil {
44
+			err = nil
45
+			result = deprecatedResponse
46
+		}
47
+	}
48
+
49
+	return
50
+}
0 51
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package client
1
+
2
+import (
3
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
4
+
5
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
6
+)
7
+
8
+type LocalSubjectAccessReviewsImpersonator interface {
9
+	ImpersonateLocalSubjectAccessReviews(namespace, token string) LocalSubjectAccessReviewInterface
10
+}
11
+
12
+// LocalSubjectAccessReviewsNamespacer has methods to work with LocalSubjectAccessReview resources in a namespace
13
+type LocalSubjectAccessReviewsNamespacer interface {
14
+	LocalSubjectAccessReviews(namespace string) LocalSubjectAccessReviewInterface
15
+}
16
+
17
+// LocalSubjectAccessReviewInterface exposes methods on LocalSubjectAccessReview resources.
18
+type LocalSubjectAccessReviewInterface interface {
19
+	Create(policy *authorizationapi.LocalSubjectAccessReview) (*authorizationapi.SubjectAccessReviewResponse, error)
20
+}
21
+
22
+// localSubjectAccessReviews implements LocalSubjectAccessReviewsNamespacer interface
23
+type localSubjectAccessReviews struct {
24
+	r     *Client
25
+	ns    string
26
+	token string
27
+}
28
+
29
+// newImpersonatingLocalSubjectAccessReviews returns a subjectAccessReviews
30
+func newImpersonatingLocalSubjectAccessReviews(c *Client, namespace, token string) *localSubjectAccessReviews {
31
+	return &localSubjectAccessReviews{
32
+		r:     c,
33
+		ns:    namespace,
34
+		token: token,
35
+	}
36
+}
37
+
38
+// newLocalSubjectAccessReviews returns a localSubjectAccessReviews
39
+func newLocalSubjectAccessReviews(c *Client, namespace string) *localSubjectAccessReviews {
40
+	return &localSubjectAccessReviews{
41
+		r:  c,
42
+		ns: namespace,
43
+	}
44
+}
45
+
46
+func (c *localSubjectAccessReviews) Create(sar *authorizationapi.LocalSubjectAccessReview) (result *authorizationapi.SubjectAccessReviewResponse, err error) {
47
+	result = &authorizationapi.SubjectAccessReviewResponse{}
48
+	err = overrideAuth(c.token, c.r.Post().Namespace(c.ns).Resource("localSubjectAccessReviews")).Body(sar).Do().Into(result)
49
+
50
+	// if we get one of these failures, we may be talking to an older openshift.  In that case, we need to try hitting ns/namespace-name/subjectaccessreview
51
+	if kapierrors.IsForbidden(err) || kapierrors.IsNotFound(err) {
52
+		deprecatedSAR := &authorizationapi.SubjectAccessReview{
53
+			Action: sar.Action,
54
+			User:   sar.User,
55
+			Groups: sar.Groups,
56
+		}
57
+		deprecatedResponse := &authorizationapi.SubjectAccessReviewResponse{}
58
+		deprecatedAttemptErr := overrideAuth(c.token, c.r.Post().Namespace(c.ns).Resource("subjectAccessReviews")).Body(deprecatedSAR).Do().Into(deprecatedResponse)
59
+		if deprecatedAttemptErr == nil {
60
+			err = nil
61
+			result = deprecatedResponse
62
+		}
63
+	}
64
+
65
+	return
66
+}
... ...
@@ -1,17 +1,14 @@
1 1
 package client
2 2
 
3 3
 import (
4
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
5
+
4 6
 	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
5 7
 )
6 8
 
7
-// ResourceAccessReviewsNamespacer has methods to work with ResourceAccessReview resources in a namespace
8
-type ResourceAccessReviewsNamespacer interface {
9
-	ResourceAccessReviews(namespace string) ResourceAccessReviewInterface
10
-}
11
-
12
-// ClusterResourceAccessReviews has methods to work with ResourceAccessReview resources in the cluster scope
13
-type ClusterResourceAccessReviews interface {
14
-	ClusterResourceAccessReviews() ResourceAccessReviewInterface
9
+// ResourceAccessReviews has methods to work with ResourceAccessReview resources in the cluster scope
10
+type ResourceAccessReviews interface {
11
+	ResourceAccessReviews() ResourceAccessReviewInterface
15 12
 }
16 13
 
17 14
 // ResourceAccessReviewInterface exposes methods on ResourceAccessReview resources.
... ...
@@ -19,42 +16,46 @@ type ResourceAccessReviewInterface interface {
19 19
 	Create(policy *authorizationapi.ResourceAccessReview) (*authorizationapi.ResourceAccessReviewResponse, error)
20 20
 }
21 21
 
22
-// resourceAccessReviews implements ResourceAccessReviewsNamespacer interface
22
+// resourceAccessReviews implements ResourceAccessReviews interface
23 23
 type resourceAccessReviews struct {
24
-	r  *Client
25
-	ns string
24
+	r *Client
26 25
 }
27 26
 
28 27
 // newResourceAccessReviews returns a resourceAccessReviews
29
-func newResourceAccessReviews(c *Client, namespace string) *resourceAccessReviews {
28
+func newResourceAccessReviews(c *Client) *resourceAccessReviews {
30 29
 	return &resourceAccessReviews{
31
-		r:  c,
32
-		ns: namespace,
30
+		r: c,
33 31
 	}
34 32
 }
35 33
 
36
-// Create creates new policy. Returns the server's representation of the policy and error if one occurs.
37
-func (c *resourceAccessReviews) Create(policy *authorizationapi.ResourceAccessReview) (result *authorizationapi.ResourceAccessReviewResponse, err error) {
34
+func (c *resourceAccessReviews) Create(rar *authorizationapi.ResourceAccessReview) (result *authorizationapi.ResourceAccessReviewResponse, err error) {
38 35
 	result = &authorizationapi.ResourceAccessReviewResponse{}
39
-	err = c.r.Post().Namespace(c.ns).Resource("resourceAccessReviews").Body(policy).Do().Into(result)
40
-	return
41
-}
42 36
 
43
-// clusterResourceAccessReviews implements ClusterResourceAccessReviews interface
44
-type clusterResourceAccessReviews struct {
45
-	r *Client
46
-}
37
+	// if this a cluster RAR, then no special handling
38
+	if len(rar.Action.Namespace) == 0 {
39
+		err = c.r.Post().Resource("resourceAccessReviews").Body(rar).Do().Into(result)
40
+		return
41
+	}
47 42
 
48
-// newClusterResourceAccessReviews returns a clusterResourceAccessReviews
49
-func newClusterResourceAccessReviews(c *Client) *clusterResourceAccessReviews {
50
-	return &clusterResourceAccessReviews{
51
-		r: c,
43
+	err = c.r.Post().Resource("resourceAccessReviews").Body(rar).Do().Into(result)
44
+
45
+	// if the namespace values don't match then we definitely hit an old server.  If we got a forbidden, then we might have hit an old server
46
+	// and should try the old endpoint
47
+	if (rar.Action.Namespace != result.Namespace) || kapierrors.IsForbidden(err) {
48
+		deprecatedResponse := &authorizationapi.ResourceAccessReviewResponse{}
49
+		deprecatedAttemptErr := c.r.Post().Namespace(rar.Action.Namespace).Resource("resourceAccessReviews").Body(rar).Do().Into(deprecatedResponse)
50
+
51
+		// if we definitely hit an old server, then return the error and result you get from the older server.
52
+		if rar.Action.Namespace != result.Namespace {
53
+			return deprecatedResponse, deprecatedAttemptErr
54
+		}
55
+
56
+		// if we're not certain it was an old server, success overwrites the previous error, but failure doesn't overwrite the previous error
57
+		if deprecatedAttemptErr == nil {
58
+			err = nil
59
+			result = deprecatedResponse
60
+		}
52 61
 	}
53
-}
54 62
 
55
-// Create creates new policy. Returns the server's representation of the policy and error if one occurs.
56
-func (c *clusterResourceAccessReviews) Create(policy *authorizationapi.ResourceAccessReview) (result *authorizationapi.ResourceAccessReviewResponse, err error) {
57
-	result = &authorizationapi.ResourceAccessReviewResponse{}
58
-	err = c.r.Post().Resource("resourceAccessReviews").Body(policy).Do().Into(result)
59 63
 	return
60 64
 }
... ...
@@ -3,23 +3,19 @@ package client
3 3
 import (
4 4
 	"fmt"
5 5
 
6
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
6 7
 	"k8s.io/kubernetes/pkg/client"
7 8
 
8 9
 	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
9 10
 )
10 11
 
11
-// SubjectAccessReviewsNamespacer has methods to work with SubjectAccessReview resources in a namespace
12
-type SubjectAccessReviewsNamespacer interface {
13
-	SubjectAccessReviews(namespace string) SubjectAccessReviewInterface
14
-}
15
-
16 12
 type SubjectAccessReviewsImpersonator interface {
17
-	ImpersonateSubjectAccessReviews(namespace, token string) SubjectAccessReviewInterface
13
+	ImpersonateSubjectAccessReviews(token string) SubjectAccessReviewInterface
18 14
 }
19 15
 
20
-// ClusterSubjectAccessReviews has methods to work with SubjectAccessReview resources in the cluster scope
21
-type ClusterSubjectAccessReviews interface {
22
-	ClusterSubjectAccessReviews() SubjectAccessReviewInterface
16
+// SubjectAccessReviews has methods to work with SubjectAccessReview resources in the cluster scope
17
+type SubjectAccessReviews interface {
18
+	SubjectAccessReviews() SubjectAccessReviewInterface
23 19
 }
24 20
 
25 21
 // SubjectAccessReviewInterface exposes methods on SubjectAccessReview resources.
... ...
@@ -27,45 +23,56 @@ type SubjectAccessReviewInterface interface {
27 27
 	Create(policy *authorizationapi.SubjectAccessReview) (*authorizationapi.SubjectAccessReviewResponse, error)
28 28
 }
29 29
 
30
-// subjectAccessReviews implements SubjectAccessReviewsNamespacer interface
30
+// subjectAccessReviews implements SubjectAccessReviews interface
31 31
 type subjectAccessReviews struct {
32 32
 	r     *Client
33
-	ns    string
34 33
 	token string
35 34
 }
36 35
 
37
-// newSubjectAccessReviews returns a subjectAccessReviews
38
-func newSubjectAccessReviews(c *Client, namespace, token string) *subjectAccessReviews {
36
+// newImpersonatingSubjectAccessReviews returns a subjectAccessReviews
37
+func newImpersonatingSubjectAccessReviews(c *Client, token string) *subjectAccessReviews {
39 38
 	return &subjectAccessReviews{
40 39
 		r:     c,
41
-		ns:    namespace,
42 40
 		token: token,
43 41
 	}
44 42
 }
45 43
 
46
-// Create creates new policy. Returns the server's representation of the policy and error if one occurs.
47
-func (c *subjectAccessReviews) Create(policy *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReviewResponse, err error) {
48
-	result = &authorizationapi.SubjectAccessReviewResponse{}
49
-	err = overrideAuth(c.token, c.r.Post().Namespace(c.ns).Resource("subjectAccessReviews")).Body(policy).Do().Into(result)
50
-	return
51
-}
52
-
53
-// clusterSubjectAccessReviews implements ClusterSubjectAccessReviews interface
54
-type clusterSubjectAccessReviews struct {
55
-	r *Client
56
-}
57
-
58
-// newClusterSubjectAccessReviews returns a clusterSubjectAccessReviews
59
-func newClusterSubjectAccessReviews(c *Client) *clusterSubjectAccessReviews {
60
-	return &clusterSubjectAccessReviews{
44
+// newSubjectAccessReviews returns a subjectAccessReviews
45
+func newSubjectAccessReviews(c *Client) *subjectAccessReviews {
46
+	return &subjectAccessReviews{
61 47
 		r: c,
62 48
 	}
63 49
 }
64 50
 
65
-// Create creates new policy. Returns the server's representation of the policy and error if one occurs.
66
-func (c *clusterSubjectAccessReviews) Create(policy *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReviewResponse, err error) {
51
+func (c *subjectAccessReviews) Create(sar *authorizationapi.SubjectAccessReview) (result *authorizationapi.SubjectAccessReviewResponse, err error) {
67 52
 	result = &authorizationapi.SubjectAccessReviewResponse{}
68
-	err = c.r.Post().Resource("subjectAccessReviews").Body(policy).Do().Into(result)
53
+
54
+	// if this a cluster SAR, then no special handling
55
+	if len(sar.Action.Namespace) == 0 {
56
+		err = overrideAuth(c.token, c.r.Post().Resource("subjectAccessReviews")).Body(sar).Do().Into(result)
57
+		return
58
+	}
59
+
60
+	err = c.r.Post().Resource("subjectAccessReviews").Body(sar).Do().Into(result)
61
+
62
+	// if the namespace values don't match then we definitely hit an old server.  If we got a forbidden, then we might have hit an old server
63
+	// and should try the old endpoint
64
+	if (sar.Action.Namespace != result.Namespace) || kapierrors.IsForbidden(err) {
65
+		deprecatedResponse := &authorizationapi.SubjectAccessReviewResponse{}
66
+		deprecatedAttemptErr := overrideAuth(c.token, c.r.Post().Namespace(sar.Action.Namespace).Resource("subjectAccessReviews")).Body(sar).Do().Into(deprecatedResponse)
67
+
68
+		// if we definitely hit an old server, then return the error and result you get from the older server.
69
+		if sar.Action.Namespace != result.Namespace {
70
+			return deprecatedResponse, deprecatedAttemptErr
71
+		}
72
+
73
+		// if we're not certain it was an old server, success overwrites the previous error, but failure doesn't overwrite the previous error
74
+		if deprecatedAttemptErr == nil {
75
+			err = nil
76
+			result = deprecatedResponse
77
+		}
78
+	}
79
+
69 80
 	return
70 81
 }
71 82
 
... ...
@@ -211,24 +211,24 @@ func (c *Fake) PolicyBindings(namespace string) client.PolicyBindingInterface {
211 211
 	return &FakePolicyBindings{Fake: c, Namespace: namespace}
212 212
 }
213 213
 
214
-// ResourceAccessReviews provides a fake REST client for ResourceAccessReviews
215
-func (c *Fake) ResourceAccessReviews(namespace string) client.ResourceAccessReviewInterface {
216
-	return &FakeResourceAccessReviews{Fake: c, Namespace: namespace}
214
+// LocalResourceAccessReviews provides a fake REST client for ResourceAccessReviews
215
+func (c *Fake) LocalResourceAccessReviews(namespace string) client.LocalResourceAccessReviewInterface {
216
+	return &FakeLocalResourceAccessReviews{Fake: c}
217 217
 }
218 218
 
219
-// ClusterResourceAccessReviews provides a fake REST client for ClusterResourceAccessReviews
220
-func (c *Fake) ClusterResourceAccessReviews() client.ResourceAccessReviewInterface {
219
+// ResourceAccessReviews provides a fake REST client for ClusterResourceAccessReviews
220
+func (c *Fake) ResourceAccessReviews() client.ResourceAccessReviewInterface {
221 221
 	return &FakeClusterResourceAccessReviews{Fake: c}
222 222
 }
223 223
 
224
-// SubjectAccessReviews provides a fake REST client for SubjectAccessReviews
225
-func (c *Fake) SubjectAccessReviews(namespace string) client.SubjectAccessReviewInterface {
226
-	return &FakeSubjectAccessReviews{Fake: c, Namespace: namespace}
224
+// ImpersonateSubjectAccessReviews provides a fake REST client for SubjectAccessReviews
225
+func (c *Fake) ImpersonateSubjectAccessReviews(token string) client.SubjectAccessReviewInterface {
226
+	return &FakeClusterSubjectAccessReviews{Fake: c}
227 227
 }
228 228
 
229 229
 // ImpersonateSubjectAccessReviews provides a fake REST client for SubjectAccessReviews
230
-func (c *Fake) ImpersonateSubjectAccessReviews(token, namespace string) client.SubjectAccessReviewInterface {
231
-	return &FakeSubjectAccessReviews{Fake: c, Namespace: namespace}
230
+func (c *Fake) ImpersonateLocalSubjectAccessReviews(namespace, token string) client.LocalSubjectAccessReviewInterface {
231
+	return &FakeLocalSubjectAccessReviews{Fake: c, Namespace: namespace}
232 232
 }
233 233
 
234 234
 // OAuthAccessTokens provides a fake REST client for OAuthAccessTokens
... ...
@@ -236,8 +236,13 @@ func (c *Fake) OAuthAccessTokens() client.OAuthAccessTokenInterface {
236 236
 	return &FakeOAuthAccessTokens{Fake: c}
237 237
 }
238 238
 
239
-// ClusterSubjectAccessReviews provides a fake REST client for ClusterSubjectAccessReviews
240
-func (c *Fake) ClusterSubjectAccessReviews() client.SubjectAccessReviewInterface {
239
+// LocalSubjectAccessReviews provides a fake REST client for SubjectAccessReviews
240
+func (c *Fake) LocalSubjectAccessReviews(namespace string) client.LocalSubjectAccessReviewInterface {
241
+	return &FakeLocalSubjectAccessReviews{Fake: c}
242
+}
243
+
244
+// SubjectAccessReviews provides a fake REST client for ClusterSubjectAccessReviews
245
+func (c *Fake) SubjectAccessReviews() client.SubjectAccessReviewInterface {
241 246
 	return &FakeClusterSubjectAccessReviews{Fake: c}
242 247
 }
243 248
 
244 249
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+package testclient
1
+
2
+import (
3
+	ktestclient "k8s.io/kubernetes/pkg/client/testclient"
4
+
5
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
6
+)
7
+
8
+type FakeLocalResourceAccessReviews struct {
9
+	Fake      *Fake
10
+	Namespace string
11
+}
12
+
13
+func (c *FakeLocalResourceAccessReviews) Create(inObj *authorizationapi.LocalResourceAccessReview) (*authorizationapi.ResourceAccessReviewResponse, error) {
14
+	obj, err := c.Fake.Invokes(ktestclient.NewCreateAction("localresourceaccessreviews", c.Namespace, inObj), &authorizationapi.ResourceAccessReviewResponse{})
15
+	if cast, ok := obj.(*authorizationapi.ResourceAccessReviewResponse); ok {
16
+		return cast, err
17
+	}
18
+	return nil, err
19
+}
0 20
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+package testclient
1
+
2
+import (
3
+	ktestclient "k8s.io/kubernetes/pkg/client/testclient"
4
+
5
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
6
+)
7
+
8
+type FakeLocalSubjectAccessReviews struct {
9
+	Fake      *Fake
10
+	Namespace string
11
+}
12
+
13
+func (c *FakeLocalSubjectAccessReviews) Create(inObj *authorizationapi.LocalSubjectAccessReview) (*authorizationapi.SubjectAccessReviewResponse, error) {
14
+	obj, err := c.Fake.Invokes(ktestclient.NewCreateAction("localsubjectaccessreviews", c.Namespace, inObj), &authorizationapi.SubjectAccessReviewResponse{})
15
+	if cast, ok := obj.(*authorizationapi.SubjectAccessReviewResponse); ok {
16
+		return cast, err
17
+	}
18
+	return nil, err
19
+}
... ...
@@ -6,31 +6,14 @@ import (
6 6
 	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
7 7
 )
8 8
 
9
-// FakeResourceAccessReviews implements ResourceAccessReviewInterface. Meant to be embedded into a struct to get a default
10
-// implementation. This makes faking out just the methods you want to test easier.
11
-type FakeResourceAccessReviews struct {
12
-	Fake      *Fake
13
-	Namespace string
14
-}
15
-
16
-func (c *FakeResourceAccessReviews) Create(inObj *authorizationapi.ResourceAccessReview) (*authorizationapi.ResourceAccessReviewResponse, error) {
17
-	obj, err := c.Fake.Invokes(ktestclient.NewCreateAction("resourceaccessreviews", c.Namespace, inObj), inObj)
18
-	if obj == nil {
19
-		return nil, err
20
-	}
21
-
22
-	return obj.(*authorizationapi.ResourceAccessReviewResponse), err
23
-}
24
-
25 9
 type FakeClusterResourceAccessReviews struct {
26 10
 	Fake *Fake
27 11
 }
28 12
 
29 13
 func (c *FakeClusterResourceAccessReviews) Create(inObj *authorizationapi.ResourceAccessReview) (*authorizationapi.ResourceAccessReviewResponse, error) {
30
-	obj, err := c.Fake.Invokes(ktestclient.NewRootCreateAction("resourceaccessreviews", inObj), inObj)
31
-	if obj == nil {
32
-		return nil, err
14
+	obj, err := c.Fake.Invokes(ktestclient.NewRootCreateAction("resourceaccessreviews", inObj), &authorizationapi.ResourceAccessReviewResponse{})
15
+	if cast, ok := obj.(*authorizationapi.ResourceAccessReviewResponse); ok {
16
+		return cast, err
33 17
 	}
34
-
35
-	return obj.(*authorizationapi.ResourceAccessReviewResponse), err
18
+	return nil, err
36 19
 }
... ...
@@ -6,22 +6,6 @@ import (
6 6
 	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
7 7
 )
8 8
 
9
-// FakeSubjectAccessReviews implements SubjectAccessReviewInterface. Meant to be embedded into a struct to get a default
10
-// implementation. This makes faking out just the methods you want to test easier.
11
-type FakeSubjectAccessReviews struct {
12
-	Fake      *Fake
13
-	Namespace string
14
-}
15
-
16
-func (c *FakeSubjectAccessReviews) Create(inObj *authorizationapi.SubjectAccessReview) (*authorizationapi.SubjectAccessReviewResponse, error) {
17
-	obj, err := c.Fake.Invokes(ktestclient.NewCreateAction("subjectaccessreviews", c.Namespace, inObj), inObj)
18
-	if obj == nil {
19
-		return nil, err
20
-	}
21
-
22
-	return obj.(*authorizationapi.SubjectAccessReviewResponse), err
23
-}
24
-
25 9
 // FakeClusterSubjectAccessReviews implements the ClusterSubjectAccessReviews interface.
26 10
 // Meant to be embedded into a struct to get a default implementation.
27 11
 // This makes faking out just the methods you want to test easier.
... ...
@@ -30,10 +14,9 @@ type FakeClusterSubjectAccessReviews struct {
30 30
 }
31 31
 
32 32
 func (c *FakeClusterSubjectAccessReviews) Create(inObj *authorizationapi.SubjectAccessReview) (*authorizationapi.SubjectAccessReviewResponse, error) {
33
-	obj, err := c.Fake.Invokes(ktestclient.NewRootCreateAction("subjectaccessreviews", inObj), inObj)
34
-	if obj == nil {
35
-		return nil, err
33
+	obj, err := c.Fake.Invokes(ktestclient.NewRootCreateAction("subjectaccessreviews", inObj), &authorizationapi.SubjectAccessReviewResponse{})
34
+	if cast, ok := obj.(*authorizationapi.SubjectAccessReviewResponse); ok {
35
+		return cast, err
36 36
 	}
37
-
38
-	return obj.(*authorizationapi.SubjectAccessReviewResponse), err
37
+	return nil, err
39 38
 }
... ...
@@ -21,7 +21,7 @@ const WhoCanRecommendedName = "who-can"
21 21
 type whoCanOptions struct {
22 22
 	allNamespaces    bool
23 23
 	bindingNamespace string
24
-	client           client.Interface
24
+	client           *client.Client
25 25
 
26 26
 	verb     string
27 27
 	resource string
... ...
@@ -68,18 +68,19 @@ func (o *whoCanOptions) complete(args []string) error {
68 68
 }
69 69
 
70 70
 func (o *whoCanOptions) run() error {
71
-	resourceAccessReview := &authorizationapi.ResourceAccessReview{}
72
-	resourceAccessReview.Resource = o.resource
73
-	resourceAccessReview.Verb = o.verb
71
+	authorizationAttributes := authorizationapi.AuthorizationAttributes{
72
+		Resource: o.resource,
73
+		Verb:     o.verb,
74
+	}
74 75
 
75
-	var reviewInterface client.ResourceAccessReviewInterface
76
+	resourceAccessReviewResponse := &authorizationapi.ResourceAccessReviewResponse{}
77
+	var err error
76 78
 	if o.allNamespaces {
77
-		reviewInterface = o.client.ClusterResourceAccessReviews()
79
+		resourceAccessReviewResponse, err = o.client.ResourceAccessReviews().Create(&authorizationapi.ResourceAccessReview{Action: authorizationAttributes})
78 80
 	} else {
79
-		reviewInterface = o.client.ResourceAccessReviews(o.bindingNamespace)
81
+		resourceAccessReviewResponse, err = o.client.LocalResourceAccessReviews(o.bindingNamespace).Create(&authorizationapi.LocalResourceAccessReview{Action: authorizationAttributes})
80 82
 	}
81 83
 
82
-	resourceAccessReviewResponse, err := reviewInterface.Create(resourceAccessReview)
83 84
 	if err != nil {
84 85
 		return err
85 86
 	}
... ...
@@ -21,7 +21,7 @@ The client stores configuration in the current user's home directory (under the
21 21
 config). When you login the first time, a new config file is created, and subsequent project changes with the
22 22
 'project' command will set the current context. These subcommands allow you to manage the config directly.
23 23
 
24
-Reference: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/kubeconfig-file.md`
24
+Reference: https://k8s.io/kubernetes/blob/master/docs/kubeconfig-file.md`
25 25
 
26 26
 	configExample = `  // Change the config context to use
27 27
   %[1]s %[2]s use-context my-context
... ...
@@ -45,6 +45,14 @@ var DescriberCoverageExceptions = []reflect.Type{
45 45
 	reflect.TypeOf(&deployapi.DeploymentConfigRollback{}),             // normal users don't ever look at these
46 46
 	reflect.TypeOf(&projectapi.ProjectRequest{}),                      // normal users don't ever look at these
47 47
 	reflect.TypeOf(&authorizationapi.IsPersonalSubjectAccessReview{}), // not a top level resource
48
+
49
+	// these resources can't be "GET"ed, so you can't make a describer for them
50
+	reflect.TypeOf(&authorizationapi.SubjectAccessReviewResponse{}),
51
+	reflect.TypeOf(&authorizationapi.ResourceAccessReviewResponse{}),
52
+	reflect.TypeOf(&authorizationapi.SubjectAccessReview{}),
53
+	reflect.TypeOf(&authorizationapi.ResourceAccessReview{}),
54
+	reflect.TypeOf(&authorizationapi.LocalSubjectAccessReview{}),
55
+	reflect.TypeOf(&authorizationapi.LocalResourceAccessReview{}),
48 56
 }
49 57
 
50 58
 // MissingDescriberCoverageExceptions is the list of types that were missing describer methods when I started
... ...
@@ -52,10 +60,6 @@ var DescriberCoverageExceptions = []reflect.Type{
52 52
 // TODO describers should be added for these types
53 53
 var MissingDescriberCoverageExceptions = []reflect.Type{
54 54
 	reflect.TypeOf(&imageapi.ImageStreamMapping{}),
55
-	reflect.TypeOf(&authorizationapi.SubjectAccessReviewResponse{}),
56
-	reflect.TypeOf(&authorizationapi.ResourceAccessReviewResponse{}),
57
-	reflect.TypeOf(&authorizationapi.SubjectAccessReview{}),
58
-	reflect.TypeOf(&authorizationapi.ResourceAccessReview{}),
59 55
 	reflect.TypeOf(&oauthapi.OAuthClient{}),
60 56
 	reflect.TypeOf(&sdnapi.ClusterNetwork{}),
61 57
 	reflect.TypeOf(&sdnapi.HostSubnet{}),
... ...
@@ -22,16 +22,20 @@ import (
22 22
 var PrinterCoverageExceptions = []reflect.Type{
23 23
 	reflect.TypeOf(&imageapi.DockerImage{}), // not a top level resource
24 24
 	reflect.TypeOf(&buildapi.BuildLog{}),    // just a marker type
25
+
26
+	// these resources can't be "GET"ed, so we probably don't need a printer for them
27
+	reflect.TypeOf(&authorizationapi.SubjectAccessReviewResponse{}),
28
+	reflect.TypeOf(&authorizationapi.ResourceAccessReviewResponse{}),
29
+	reflect.TypeOf(&authorizationapi.SubjectAccessReview{}),
30
+	reflect.TypeOf(&authorizationapi.ResourceAccessReview{}),
31
+	reflect.TypeOf(&authorizationapi.LocalSubjectAccessReview{}),
32
+	reflect.TypeOf(&authorizationapi.LocalResourceAccessReview{}),
25 33
 }
26 34
 
27 35
 // MissingPrinterCoverageExceptions is the list of types that were missing printer methods when I started
28 36
 // You should never add to this list
29 37
 // TODO printers should be added for these types
30 38
 var MissingPrinterCoverageExceptions = []reflect.Type{
31
-	reflect.TypeOf(&authorizationapi.SubjectAccessReviewResponse{}),
32
-	reflect.TypeOf(&authorizationapi.ResourceAccessReviewResponse{}),
33
-	reflect.TypeOf(&authorizationapi.SubjectAccessReview{}),
34
-	reflect.TypeOf(&authorizationapi.ResourceAccessReview{}),
35 39
 	reflect.TypeOf(&deployapi.DeploymentConfigRollback{}),
36 40
 	reflect.TypeOf(&imageapi.ImageStreamMapping{}),
37 41
 	reflect.TypeOf(&buildapi.BuildLogOptions{}),
... ...
@@ -125,7 +125,7 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
125 125
 				{Verbs: util.NewStringSet("list"), Resources: util.NewStringSet("projectrequests")},
126 126
 				{Verbs: util.NewStringSet("list", "get"), Resources: util.NewStringSet("clusterroles")},
127 127
 				{Verbs: util.NewStringSet("list"), Resources: util.NewStringSet("projects")},
128
-				{Verbs: util.NewStringSet("create"), Resources: util.NewStringSet("subjectaccessreviews"), AttributeRestrictions: runtime.EmbeddedObject{Object: &authorizationapi.IsPersonalSubjectAccessReview{}}},
128
+				{Verbs: util.NewStringSet("create"), Resources: util.NewStringSet("subjectaccessreviews", "localsubjectaccessreviews"), AttributeRestrictions: runtime.EmbeddedObject{Object: &authorizationapi.IsPersonalSubjectAccessReview{}}},
129 129
 			},
130 130
 		},
131 131
 		{
... ...
@@ -77,11 +77,13 @@ import (
77 77
 	clusterpolicybindingstorage "github.com/openshift/origin/pkg/authorization/registry/clusterpolicybinding/etcd"
78 78
 	clusterrolestorage "github.com/openshift/origin/pkg/authorization/registry/clusterrole/proxy"
79 79
 	clusterrolebindingstorage "github.com/openshift/origin/pkg/authorization/registry/clusterrolebinding/proxy"
80
+	"github.com/openshift/origin/pkg/authorization/registry/localresourceaccessreview"
81
+	"github.com/openshift/origin/pkg/authorization/registry/localsubjectaccessreview"
80 82
 	policyregistry "github.com/openshift/origin/pkg/authorization/registry/policy"
81 83
 	policyetcd "github.com/openshift/origin/pkg/authorization/registry/policy/etcd"
82 84
 	policybindingregistry "github.com/openshift/origin/pkg/authorization/registry/policybinding"
83 85
 	policybindingetcd "github.com/openshift/origin/pkg/authorization/registry/policybinding/etcd"
84
-	resourceaccessreviewregistry "github.com/openshift/origin/pkg/authorization/registry/resourceaccessreview"
86
+	"github.com/openshift/origin/pkg/authorization/registry/resourceaccessreview"
85 87
 	rolestorage "github.com/openshift/origin/pkg/authorization/registry/role/policybased"
86 88
 	rolebindingstorage "github.com/openshift/origin/pkg/authorization/registry/rolebinding/policybased"
87 89
 	"github.com/openshift/origin/pkg/authorization/registry/subjectaccessreview"
... ...
@@ -359,6 +361,10 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage {
359 359
 
360 360
 	subjectAccessReviewStorage := subjectaccessreview.NewREST(c.Authorizer)
361 361
 	subjectAccessReviewRegistry := subjectaccessreview.NewRegistry(subjectAccessReviewStorage)
362
+	localSubjectAccessReviewStorage := localsubjectaccessreview.NewREST(subjectAccessReviewRegistry)
363
+	resourceAccessReviewStorage := resourceaccessreview.NewREST(c.Authorizer)
364
+	resourceAccessReviewRegistry := resourceaccessreview.NewRegistry(resourceAccessReviewStorage)
365
+	localResourceAccessReviewStorage := localresourceaccessreview.NewREST(resourceAccessReviewRegistry)
362 366
 
363 367
 	imageStorage := imageetcd.NewREST(c.EtcdHelper)
364 368
 	imageRegistry := image.NewRegistry(imageStorage)
... ...
@@ -455,8 +461,10 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage {
455 455
 		"oAuthClients":              clientetcd.NewREST(c.EtcdHelper),
456 456
 		"oAuthClientAuthorizations": clientauthetcd.NewREST(c.EtcdHelper),
457 457
 
458
-		"resourceAccessReviews": resourceaccessreviewregistry.NewREST(c.Authorizer),
459
-		"subjectAccessReviews":  subjectAccessReviewStorage,
458
+		"resourceAccessReviews":      resourceAccessReviewStorage,
459
+		"subjectAccessReviews":       subjectAccessReviewStorage,
460
+		"localSubjectAccessReviews":  localSubjectAccessReviewStorage,
461
+		"localResourceAccessReviews": localResourceAccessReviewStorage,
460 462
 
461 463
 		"policies":       policyStorage,
462 464
 		"policyBindings": policyBindingStorage,
... ...
@@ -87,7 +87,7 @@ func validateDeploymentStrategy(strategy *deployapi.DeploymentStrategy) fielderr
87 87
 		}
88 88
 	}
89 89
 
90
-	// TODO: validate resource requirements (prereq: https://github.com/GoogleCloudPlatform/kubernetes/pull/7059)
90
+	// TODO: validate resource requirements (prereq: https://k8s.io/kubernetes/pull/7059)
91 91
 
92 92
 	return errs
93 93
 }
... ...
@@ -20,7 +20,7 @@ import (
20 20
 )
21 21
 
22 22
 // TODO: This should perhaps be made public upstream. See:
23
-// https://github.com/GoogleCloudPlatform/kubernetes/issues/7851
23
+// https://k8s.io/kubernetes/issues/7851
24 24
 const sourceIdAnnotation = "kubectl.kubernetes.io/update-source-id"
25 25
 
26 26
 // RollingDeploymentStrategy is a Strategy which implements rolling
... ...
@@ -35,8 +35,8 @@ const sourceIdAnnotation = "kubectl.kubernetes.io/update-source-id"
35 35
 // These caveats can be resolved with future upstream refactorings to
36 36
 // RollingUpdater[1][2].
37 37
 //
38
-// [1] https://github.com/GoogleCloudPlatform/kubernetes/pull/7183
39
-// [2] https://github.com/GoogleCloudPlatform/kubernetes/issues/7851
38
+// [1] https://k8s.io/kubernetes/pull/7183
39
+// [2] https://k8s.io/kubernetes/issues/7851
40 40
 type RollingDeploymentStrategy struct {
41 41
 	// initialStrategy is used when there are no prior deployments.
42 42
 	initialStrategy acceptingDeploymentStrategy
... ...
@@ -56,7 +56,7 @@ type RollingDeploymentStrategy struct {
56 56
 // acceptingDeploymentStrategy is a DeploymentStrategy which accepts an
57 57
 // injected UpdateAcceptor as part of the deploy function. This is a hack to
58 58
 // support using the Recreate strategy for initial deployments and should be
59
-// removed when https://github.com/GoogleCloudPlatform/kubernetes/pull/7183 is
59
+// removed when https://k8s.io/kubernetes/pull/7183 is
60 60
 // fixed.
61 61
 type acceptingDeploymentStrategy interface {
62 62
 	DeployWithAcceptor(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error
... ...
@@ -174,7 +174,7 @@ func (s *RollingDeploymentStrategy) Deploy(from *kapi.ReplicationController, to
174 174
 	// unless it already exists on the deployment.
175 175
 	//
176 176
 	// Related upstream issue:
177
-	// https://github.com/GoogleCloudPlatform/kubernetes/pull/7183
177
+	// https://k8s.io/kubernetes/pull/7183
178 178
 	to, err = s.client.GetReplicationController(to.Namespace, to.Name)
179 179
 	if err != nil {
180 180
 		return fmt.Errorf("couldn't look up deployment %s: %s", deployutil.LabelForDeployment(to), err)
... ...
@@ -194,7 +194,7 @@ func (s *RollingDeploymentStrategy) Deploy(from *kapi.ReplicationController, to
194 194
 	// on the RC. For now, fake it out by just setting replicas to 1.
195 195
 	//
196 196
 	// Related upstream issue:
197
-	// https://github.com/GoogleCloudPlatform/kubernetes/pull/7183
197
+	// https://k8s.io/kubernetes/pull/7183
198 198
 	to.Spec.Replicas = 1
199 199
 
200 200
 	// Perform a rolling update.
... ...
@@ -250,12 +250,15 @@ func verifyOpenShiftUser(client *client.Client) error {
250 250
 }
251 251
 
252 252
 func verifyImageStreamAccess(namespace, imageRepo, verb string, client *client.Client) error {
253
-	sar := authorizationapi.SubjectAccessReview{
254
-		Verb:         verb,
255
-		Resource:     "imagestreams/layers",
256
-		ResourceName: imageRepo,
253
+	sar := authorizationapi.LocalSubjectAccessReview{
254
+		Action: authorizationapi.AuthorizationAttributes{
255
+			Verb:         verb,
256
+			Resource:     "imagestreams/layers",
257
+			ResourceName: imageRepo,
258
+		},
257 259
 	}
258
-	response, err := client.SubjectAccessReviews(namespace).Create(&sar)
260
+	response, err := client.LocalSubjectAccessReviews(namespace).Create(&sar)
261
+
259 262
 	if err != nil {
260 263
 		log.Errorf("OpenShift client error: %s", err)
261 264
 		if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) {
... ...
@@ -263,19 +266,23 @@ func verifyImageStreamAccess(namespace, imageRepo, verb string, client *client.C
263 263
 		}
264 264
 		return err
265 265
 	}
266
+
266 267
 	if !response.Allowed {
267 268
 		log.Errorf("OpenShift access denied: %s", response.Reason)
268 269
 		return ErrOpenShiftAccessDenied
269 270
 	}
271
+
270 272
 	return nil
271 273
 }
272 274
 
273 275
 func verifyPruneAccess(client *client.Client) error {
274 276
 	sar := authorizationapi.SubjectAccessReview{
275
-		Verb:     "delete",
276
-		Resource: "images",
277
+		Action: authorizationapi.AuthorizationAttributes{
278
+			Verb:     "delete",
279
+			Resource: "images",
280
+		},
277 281
 	}
278
-	response, err := client.ClusterSubjectAccessReviews().Create(&sar)
282
+	response, err := client.SubjectAccessReviews().Create(&sar)
279 283
 	if err != nil {
280 284
 		log.Errorf("OpenShift client error: %s", err)
281 285
 		if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) {
... ...
@@ -174,9 +174,9 @@ func TestAccessController(t *testing.T) {
174 174
 			openshiftResponses: []response{
175 175
 				{500, "Uh oh"},
176 176
 			},
177
-			expectedError:     errors.New("an error on the server has prevented the request from succeeding (post subjectAccessReviews)"),
177
+			expectedError:     errors.New("an error on the server has prevented the request from succeeding (post localSubjectAccessReviews)"),
178 178
 			expectedChallenge: false,
179
-			expectedActions:   []string{"POST /oapi/v1/namespaces/foo/subjectaccessreviews"},
179
+			expectedActions:   []string{"POST /oapi/v1/namespaces/foo/localsubjectaccessreviews"},
180 180
 		},
181 181
 		"valid openshift token but token not scoped for the given repo operation": {
182 182
 			access: []auth.Access{{
... ...
@@ -192,7 +192,7 @@ func TestAccessController(t *testing.T) {
192 192
 			},
193 193
 			expectedError:     ErrOpenShiftAccessDenied,
194 194
 			expectedChallenge: true,
195
-			expectedActions:   []string{"POST /oapi/v1/namespaces/foo/subjectaccessreviews"},
195
+			expectedActions:   []string{"POST /oapi/v1/namespaces/foo/localsubjectaccessreviews"},
196 196
 		},
197 197
 		"partially valid openshift token": {
198 198
 			// Check all the different resource-type/verb combinations we allow to make sure they validate and continue to validate remaining Resource requests
... ...
@@ -212,10 +212,10 @@ func TestAccessController(t *testing.T) {
212 212
 			expectedError:     ErrOpenShiftAccessDenied,
213 213
 			expectedChallenge: true,
214 214
 			expectedActions: []string{
215
-				"POST /oapi/v1/namespaces/foo/subjectaccessreviews",
216
-				"POST /oapi/v1/namespaces/bar/subjectaccessreviews",
215
+				"POST /oapi/v1/namespaces/foo/localsubjectaccessreviews",
216
+				"POST /oapi/v1/namespaces/bar/localsubjectaccessreviews",
217 217
 				"POST /oapi/v1/subjectaccessreviews",
218
-				"POST /oapi/v1/namespaces/baz/subjectaccessreviews",
218
+				"POST /oapi/v1/namespaces/baz/localsubjectaccessreviews",
219 219
 			},
220 220
 		},
221 221
 		"valid openshift token": {
... ...
@@ -232,7 +232,7 @@ func TestAccessController(t *testing.T) {
232 232
 			},
233 233
 			expectedError:     nil,
234 234
 			expectedChallenge: false,
235
-			expectedActions:   []string{"POST /oapi/v1/namespaces/foo/subjectaccessreviews"},
235
+			expectedActions:   []string{"POST /oapi/v1/namespaces/foo/localsubjectaccessreviews"},
236 236
 		},
237 237
 		"pruning": {
238 238
 			access: []auth.Access{
... ...
@@ -200,17 +200,19 @@ func NewEnviromentConfig() (*Config, error) {
200 200
 			if !info.Push && allowAnonymousGet {
201 201
 				return true, nil
202 202
 			}
203
-			req := &authapi.SubjectAccessReview{
204
-				Verb:     "get",
205
-				Resource: "pods",
203
+			req := &authapi.LocalSubjectAccessReview{
204
+				Action: authapi.AuthorizationAttributes{
205
+					Verb:     "get",
206
+					Resource: "pods",
207
+				},
206 208
 			}
207 209
 			if info.Push {
208 210
 				if !config.AllowPush {
209 211
 					return false, nil
210 212
 				}
211
-				req.Verb = "create"
213
+				req.Action.Verb = "create"
212 214
 			}
213
-			res, err := osc.ImpersonateSubjectAccessReviews(namespace, info.Password).Create(req)
215
+			res, err := osc.ImpersonateLocalSubjectAccessReviews(namespace, info.Password).Create(req)
214 216
 			if err != nil {
215 217
 				return false, err
216 218
 			}
... ...
@@ -309,11 +309,13 @@ func (v *TagVerifier) Verify(old, stream *api.ImageStream, user user.Info) field
309 309
 		}
310 310
 
311 311
 		subjectAccessReview := authorizationapi.SubjectAccessReview{
312
-			Verb:         "get",
313
-			Resource:     "imagestreams",
314
-			User:         user.GetName(),
315
-			Groups:       util.NewStringSet(user.GetGroups()...),
316
-			ResourceName: streamName,
312
+			Action: authorizationapi.AuthorizationAttributes{
313
+				Verb:         "get",
314
+				Resource:     "imagestreams",
315
+				ResourceName: streamName,
316
+			},
317
+			User:   user.GetName(),
318
+			Groups: util.NewStringSet(user.GetGroups()...),
317 319
 		}
318 320
 		ctx := kapi.WithNamespace(kapi.NewContext(), tagRef.From.Namespace)
319 321
 		glog.V(1).Infof("Performing SubjectAccessReview for user=%s, groups=%v to %s/%s", user.GetName(), user.GetGroups(), tagRef.From.Namespace, streamName)
... ...
@@ -282,11 +282,13 @@ func TestTagVerifier(t *testing.T) {
282 282
 				t.Errorf("%s: sar namespace: expected %v, got %v", name, e, a)
283 283
 			}
284 284
 			expectedSar := &authorizationapi.SubjectAccessReview{
285
-				Verb:         "get",
286
-				Resource:     "imagestreams",
287
-				User:         "user",
288
-				Groups:       util.NewStringSet("group1"),
289
-				ResourceName: "otherstream",
285
+				Action: authorizationapi.AuthorizationAttributes{
286
+					Verb:         "get",
287
+					Resource:     "imagestreams",
288
+					ResourceName: "otherstream",
289
+				},
290
+				User:   "user",
291
+				Groups: util.NewStringSet("group1"),
290 292
 			}
291 293
 			if e, a := expectedSar, sar.request; !reflect.DeepEqual(e, a) {
292 294
 				t.Errorf("%s: unexpected SAR request: %s", name, util.ObjectDiff(e, a))
... ...
@@ -32,11 +32,11 @@ type Reviewer interface {
32 32
 
33 33
 // reviewer performs access reviews for a project by name
34 34
 type reviewer struct {
35
-	resourceAccessReviewsNamespacer client.ResourceAccessReviewsNamespacer
35
+	resourceAccessReviewsNamespacer client.LocalResourceAccessReviewsNamespacer
36 36
 }
37 37
 
38 38
 // NewReviewer knows how to make access control reviews for a resource by name
39
-func NewReviewer(resourceAccessReviewsNamespacer client.ResourceAccessReviewsNamespacer) Reviewer {
39
+func NewReviewer(resourceAccessReviewsNamespacer client.LocalResourceAccessReviewsNamespacer) Reviewer {
40 40
 	return &reviewer{
41 41
 		resourceAccessReviewsNamespacer: resourceAccessReviewsNamespacer,
42 42
 	}
... ...
@@ -44,13 +44,15 @@ func NewReviewer(resourceAccessReviewsNamespacer client.ResourceAccessReviewsNam
44 44
 
45 45
 // Review performs a resource access review for the given resource by name
46 46
 func (r *reviewer) Review(name string) (Review, error) {
47
-	resourceAccessReview := &authorizationapi.ResourceAccessReview{
48
-		Verb:         "get",
49
-		Resource:     "namespaces",
50
-		ResourceName: name,
47
+	resourceAccessReview := &authorizationapi.LocalResourceAccessReview{
48
+		Action: authorizationapi.AuthorizationAttributes{
49
+			Verb:         "get",
50
+			Resource:     "namespaces",
51
+			ResourceName: name,
52
+		},
51 53
 	}
52 54
 
53
-	response, err := r.resourceAccessReviewsNamespacer.ResourceAccessReviews(name).Create(resourceAccessReview)
55
+	response, err := r.resourceAccessReviewsNamespacer.LocalResourceAccessReviews(name).Create(resourceAccessReview)
54 56
 
55 57
 	if err != nil {
56 58
 		return nil, err
... ...
@@ -154,12 +154,14 @@ func (r *REST) List(ctx kapi.Context, label labels.Selector, field fields.Select
154 154
 	// the caller might not have permission to run a subject access review (he has it by default, but it could have been removed).
155 155
 	// So we'll escalate for the subject access review to determine rights
156 156
 	accessReview := &authorizationapi.SubjectAccessReview{
157
-		Verb:     "create",
158
-		Resource: "projectrequests",
159
-		User:     userInfo.GetName(),
160
-		Groups:   util.NewStringSet(userInfo.GetGroups()...),
157
+		Action: authorizationapi.AuthorizationAttributes{
158
+			Verb:     "create",
159
+			Resource: "projectrequests",
160
+		},
161
+		User:   userInfo.GetName(),
162
+		Groups: util.NewStringSet(userInfo.GetGroups()...),
161 163
 	}
162
-	accessReviewResponse, err := r.openshiftClient.ClusterSubjectAccessReviews().Create(accessReview)
164
+	accessReviewResponse, err := r.openshiftClient.SubjectAccessReviews().Create(accessReview)
163 165
 	if err != nil {
164 166
 		return nil, err
165 167
 	}
... ...
@@ -165,6 +165,33 @@ func (test resourceAccessReviewTest) run(t *testing.T) {
165 165
 	}
166 166
 }
167 167
 
168
+type localResourceAccessReviewTest struct {
169
+	clientInterface client.LocalResourceAccessReviewInterface
170
+	review          *authorizationapi.LocalResourceAccessReview
171
+
172
+	response authorizationapi.ResourceAccessReviewResponse
173
+	err      string
174
+}
175
+
176
+func (test localResourceAccessReviewTest) run(t *testing.T) {
177
+	actualResponse, err := test.clientInterface.Create(test.review)
178
+	if len(test.err) > 0 {
179
+		if err == nil {
180
+			t.Errorf("Expected error: %v", test.err)
181
+		} else if !strings.Contains(err.Error(), test.err) {
182
+			t.Errorf("expected %v, got %v", test.err, err)
183
+		}
184
+	} else {
185
+		if err != nil {
186
+			t.Errorf("unexpected error: %v", err)
187
+		}
188
+	}
189
+
190
+	if actualResponse.Namespace != test.response.Namespace || !reflect.DeepEqual(actualResponse.Users.List(), test.response.Users.List()) || !reflect.DeepEqual(actualResponse.Groups.List(), test.response.Groups.List()) {
191
+		t.Errorf("%#v: expected %v, got %v", test.review, test.response, actualResponse)
192
+	}
193
+}
194
+
168 195
 func TestAuthorizationResourceAccessReview(t *testing.T) {
169 196
 	_, clusterAdminKubeConfig, err := testutil.StartTestMaster()
170 197
 	if err != nil {
... ...
@@ -211,12 +238,18 @@ func TestAuthorizationResourceAccessReview(t *testing.T) {
211 211
 		t.Fatalf("unexpected error: %v", err)
212 212
 	}
213 213
 
214
-	requestWhoCanViewDeployments := &authorizationapi.ResourceAccessReview{Verb: "get", Resource: "deployments"}
214
+	requestWhoCanViewDeployments := &authorizationapi.ResourceAccessReview{
215
+		Action: authorizationapi.AuthorizationAttributes{Verb: "get", Resource: "deployments"},
216
+	}
217
+
218
+	localRequestWhoCanViewDeployments := &authorizationapi.LocalResourceAccessReview{
219
+		Action: authorizationapi.AuthorizationAttributes{Verb: "get", Resource: "deployments"},
220
+	}
215 221
 
216 222
 	{
217
-		test := resourceAccessReviewTest{
218
-			clientInterface: haroldClient.ResourceAccessReviews("hammer-project"),
219
-			review:          requestWhoCanViewDeployments,
223
+		test := localResourceAccessReviewTest{
224
+			clientInterface: haroldClient.LocalResourceAccessReviews("hammer-project"),
225
+			review:          localRequestWhoCanViewDeployments,
220 226
 			response: authorizationapi.ResourceAccessReviewResponse{
221 227
 				Users:     util.NewStringSet("harold", "valerie"),
222 228
 				Groups:    globalClusterAdminGroups,
... ...
@@ -228,9 +261,9 @@ func TestAuthorizationResourceAccessReview(t *testing.T) {
228 228
 		test.run(t)
229 229
 	}
230 230
 	{
231
-		test := resourceAccessReviewTest{
232
-			clientInterface: markClient.ResourceAccessReviews("mallet-project"),
233
-			review:          requestWhoCanViewDeployments,
231
+		test := localResourceAccessReviewTest{
232
+			clientInterface: markClient.LocalResourceAccessReviews("mallet-project"),
233
+			review:          localRequestWhoCanViewDeployments,
234 234
 			response: authorizationapi.ResourceAccessReviewResponse{
235 235
 				Users:     util.NewStringSet("mark", "edgar"),
236 236
 				Groups:    globalClusterAdminGroups,
... ...
@@ -245,7 +278,7 @@ func TestAuthorizationResourceAccessReview(t *testing.T) {
245 245
 	// mark should not be able to make global access review requests
246 246
 	{
247 247
 		test := resourceAccessReviewTest{
248
-			clientInterface: markClient.ClusterResourceAccessReviews(),
248
+			clientInterface: markClient.ResourceAccessReviews(),
249 249
 			review:          requestWhoCanViewDeployments,
250 250
 			err:             "cannot ",
251 251
 		}
... ...
@@ -255,7 +288,7 @@ func TestAuthorizationResourceAccessReview(t *testing.T) {
255 255
 	// a cluster-admin should be able to make global access review requests
256 256
 	{
257 257
 		test := resourceAccessReviewTest{
258
-			clientInterface: clusterAdminClient.ClusterResourceAccessReviews(),
258
+			clientInterface: clusterAdminClient.ResourceAccessReviews(),
259 259
 			review:          requestWhoCanViewDeployments,
260 260
 			response: authorizationapi.ResourceAccessReviewResponse{
261 261
 				Users:  globalClusterAdminUsers,
... ...
@@ -268,9 +301,11 @@ func TestAuthorizationResourceAccessReview(t *testing.T) {
268 268
 }
269 269
 
270 270
 type subjectAccessReviewTest struct {
271
-	description     string
272
-	clientInterface client.SubjectAccessReviewInterface
273
-	review          *authorizationapi.SubjectAccessReview
271
+	description      string
272
+	localInterface   client.LocalSubjectAccessReviewInterface
273
+	clusterInterface client.SubjectAccessReviewInterface
274
+	localReview      *authorizationapi.LocalSubjectAccessReview
275
+	clusterReview    *authorizationapi.SubjectAccessReview
274 276
 
275 277
 	response authorizationapi.SubjectAccessReviewResponse
276 278
 	err      string
... ...
@@ -279,7 +314,13 @@ type subjectAccessReviewTest struct {
279 279
 func (test subjectAccessReviewTest) run(t *testing.T) {
280 280
 	failMessage := ""
281 281
 	err := wait.Poll(testutil.PolicyCachePollInterval, testutil.PolicyCachePollTimeout, func() (bool, error) {
282
-		actualResponse, err := test.clientInterface.Create(test.review)
282
+		var err error
283
+		var actualResponse *authorizationapi.SubjectAccessReviewResponse
284
+		if test.localReview != nil {
285
+			actualResponse, err = test.localInterface.Create(test.localReview)
286
+		} else {
287
+			actualResponse, err = test.clusterInterface.Create(test.clusterReview)
288
+		}
283 289
 		if len(test.err) > 0 {
284 290
 			if err == nil {
285 291
 				failMessage = fmt.Sprintf("%s: Expected error: %v", test.description, test.err)
... ...
@@ -298,7 +339,11 @@ func (test subjectAccessReviewTest) run(t *testing.T) {
298 298
 		if (actualResponse.Namespace != test.response.Namespace) ||
299 299
 			(actualResponse.Allowed != test.response.Allowed) ||
300 300
 			(!strings.HasPrefix(actualResponse.Reason, test.response.Reason)) {
301
-			failMessage = fmt.Sprintf("%s: from review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", test.description, test.review, &test.response, actualResponse)
301
+			if test.localReview != nil {
302
+				failMessage = fmt.Sprintf("%s: from local review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", test.description, test.localReview, &test.response, actualResponse)
303
+			} else {
304
+				failMessage = fmt.Sprintf("%s: from review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", test.description, test.clusterReview, &test.response, actualResponse)
305
+			}
302 306
 			return false, nil
303 307
 		}
304 308
 
... ...
@@ -354,11 +399,17 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
354 354
 	if err := addDanny.AddRole(); err != nil {
355 355
 		t.Errorf("unexpected error: %v", err)
356 356
 	}
357
-	askCanDannyGetProject := &authorizationapi.SubjectAccessReview{User: "danny", Verb: "get", Resource: "projects"}
357
+	askCanDannyGetProject := &authorizationapi.SubjectAccessReview{
358
+		User:   "danny",
359
+		Action: authorizationapi.AuthorizationAttributes{Verb: "get", Resource: "projects"},
360
+	}
358 361
 	subjectAccessReviewTest{
359
-		description:     "cluster admin told danny can get project default",
360
-		clientInterface: clusterAdminClient.SubjectAccessReviews("default"),
361
-		review:          askCanDannyGetProject,
362
+		description:    "cluster admin told danny can get project default",
363
+		localInterface: clusterAdminClient.LocalSubjectAccessReviews("default"),
364
+		localReview: &authorizationapi.LocalSubjectAccessReview{
365
+			User:   "danny",
366
+			Action: authorizationapi.AuthorizationAttributes{Verb: "get", Resource: "projects"},
367
+		},
362 368
 		response: authorizationapi.SubjectAccessReviewResponse{
363 369
 			Allowed:   true,
364 370
 			Reason:    "allowed by rule in default",
... ...
@@ -366,9 +417,9 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
366 366
 		},
367 367
 	}.run(t)
368 368
 	subjectAccessReviewTest{
369
-		description:     "cluster admin told danny cannot get projects cluster-wide",
370
-		clientInterface: clusterAdminClient.ClusterSubjectAccessReviews(),
371
-		review:          askCanDannyGetProject,
369
+		description:      "cluster admin told danny cannot get projects cluster-wide",
370
+		clusterInterface: clusterAdminClient.SubjectAccessReviews(),
371
+		clusterReview:    askCanDannyGetProject,
372 372
 		response: authorizationapi.SubjectAccessReviewResponse{
373 373
 			Allowed:   false,
374 374
 			Reason:    `User "danny" cannot get projects at the cluster scope`,
... ...
@@ -376,10 +427,10 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
376 376
 		},
377 377
 	}.run(t)
378 378
 	subjectAccessReviewTest{
379
-		description:     "as danny, can I make cluster subject access reviews",
380
-		clientInterface: dannyClient.ClusterSubjectAccessReviews(),
381
-		review:          askCanDannyGetProject,
382
-		err:             `User "danny" cannot create subjectaccessreviews at the cluster scope`,
379
+		description:      "as danny, can I make cluster subject access reviews",
380
+		clusterInterface: dannyClient.SubjectAccessReviews(),
381
+		clusterReview:    askCanDannyGetProject,
382
+		err:              `User "danny" cannot create subjectaccessreviews at the cluster scope`,
383 383
 	}.run(t)
384 384
 
385 385
 	addValerie := &policy.RoleModificationOptions{
... ...
@@ -402,11 +453,14 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
402 402
 		t.Fatalf("unexpected error: %v", err)
403 403
 	}
404 404
 
405
-	askCanValerieGetProject := &authorizationapi.SubjectAccessReview{User: "valerie", Verb: "get", Resource: "projects"}
405
+	askCanValerieGetProject := &authorizationapi.LocalSubjectAccessReview{
406
+		User:   "valerie",
407
+		Action: authorizationapi.AuthorizationAttributes{Verb: "get", Resource: "projects"},
408
+	}
406 409
 	subjectAccessReviewTest{
407
-		description:     "harold told valerie can get project hammer-project",
408
-		clientInterface: haroldClient.SubjectAccessReviews("hammer-project"),
409
-		review:          askCanValerieGetProject,
410
+		description:    "harold told valerie can get project hammer-project",
411
+		localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"),
412
+		localReview:    askCanValerieGetProject,
410 413
 		response: authorizationapi.SubjectAccessReviewResponse{
411 414
 			Allowed:   true,
412 415
 			Reason:    "allowed by rule in hammer-project",
... ...
@@ -414,9 +468,9 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
414 414
 		},
415 415
 	}.run(t)
416 416
 	subjectAccessReviewTest{
417
-		description:     "mark told valerie cannot get project mallet-project",
418
-		clientInterface: markClient.SubjectAccessReviews("mallet-project"),
419
-		review:          askCanValerieGetProject,
417
+		description:    "mark told valerie cannot get project mallet-project",
418
+		localInterface: markClient.LocalSubjectAccessReviews("mallet-project"),
419
+		localReview:    askCanValerieGetProject,
420 420
 		response: authorizationapi.SubjectAccessReviewResponse{
421 421
 			Allowed:   false,
422 422
 			Reason:    `User "valerie" cannot get projects in project "mallet-project"`,
... ...
@@ -424,11 +478,14 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
424 424
 		},
425 425
 	}.run(t)
426 426
 
427
-	askCanEdgarDeletePods := &authorizationapi.SubjectAccessReview{User: "edgar", Verb: "delete", Resource: "pods"}
427
+	askCanEdgarDeletePods := &authorizationapi.LocalSubjectAccessReview{
428
+		User:   "edgar",
429
+		Action: authorizationapi.AuthorizationAttributes{Verb: "delete", Resource: "pods"},
430
+	}
428 431
 	subjectAccessReviewTest{
429
-		description:     "mark told edgar can delete pods in mallet-project",
430
-		clientInterface: markClient.SubjectAccessReviews("mallet-project"),
431
-		review:          askCanEdgarDeletePods,
432
+		description:    "mark told edgar can delete pods in mallet-project",
433
+		localInterface: markClient.LocalSubjectAccessReviews("mallet-project"),
434
+		localReview:    askCanEdgarDeletePods,
432 435
 		response: authorizationapi.SubjectAccessReviewResponse{
433 436
 			Allowed:   true,
434 437
 			Reason:    "allowed by rule in mallet-project",
... ...
@@ -436,17 +493,20 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
436 436
 		},
437 437
 	}.run(t)
438 438
 	subjectAccessReviewTest{
439
-		description:     "harold denied ability to run subject access review in project mallet-project",
440
-		clientInterface: haroldClient.SubjectAccessReviews("mallet-project"),
441
-		review:          askCanEdgarDeletePods,
442
-		err:             `User "harold" cannot create subjectaccessreviews in project "mallet-project"`,
439
+		description:    "harold denied ability to run subject access review in project mallet-project",
440
+		localInterface: haroldClient.LocalSubjectAccessReviews("mallet-project"),
441
+		localReview:    askCanEdgarDeletePods,
442
+		err:            `User "harold" cannot create localsubjectaccessreviews in project "mallet-project"`,
443 443
 	}.run(t)
444 444
 
445
-	askCanHaroldUpdateProject := &authorizationapi.SubjectAccessReview{User: "harold", Verb: "update", Resource: "projects"}
445
+	askCanHaroldUpdateProject := &authorizationapi.LocalSubjectAccessReview{
446
+		User:   "harold",
447
+		Action: authorizationapi.AuthorizationAttributes{Verb: "update", Resource: "projects"},
448
+	}
446 449
 	subjectAccessReviewTest{
447
-		description:     "harold told harold can update project hammer-project",
448
-		clientInterface: haroldClient.SubjectAccessReviews("hammer-project"),
449
-		review:          askCanHaroldUpdateProject,
450
+		description:    "harold told harold can update project hammer-project",
451
+		localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"),
452
+		localReview:    askCanHaroldUpdateProject,
450 453
 		response: authorizationapi.SubjectAccessReviewResponse{
451 454
 			Allowed:   true,
452 455
 			Reason:    "allowed by rule in hammer-project",
... ...
@@ -454,11 +514,14 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
454 454
 		},
455 455
 	}.run(t)
456 456
 
457
-	askCanClusterAdminsCreateProject := &authorizationapi.SubjectAccessReview{Groups: util.NewStringSet("system:cluster-admins"), Verb: "create", Resource: "projects"}
457
+	askCanClusterAdminsCreateProject := &authorizationapi.SubjectAccessReview{
458
+		Groups: util.NewStringSet("system:cluster-admins"),
459
+		Action: authorizationapi.AuthorizationAttributes{Verb: "create", Resource: "projects"},
460
+	}
458 461
 	subjectAccessReviewTest{
459
-		description:     "cluster admin told cluster admins can create projects",
460
-		clientInterface: clusterAdminClient.ClusterSubjectAccessReviews(),
461
-		review:          askCanClusterAdminsCreateProject,
462
+		description:      "cluster admin told cluster admins can create projects",
463
+		clusterInterface: clusterAdminClient.SubjectAccessReviews(),
464
+		clusterReview:    askCanClusterAdminsCreateProject,
462 465
 		response: authorizationapi.SubjectAccessReviewResponse{
463 466
 			Allowed:   true,
464 467
 			Reason:    "allowed by cluster rule",
... ...
@@ -466,28 +529,32 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
466 466
 		},
467 467
 	}.run(t)
468 468
 	subjectAccessReviewTest{
469
-		description:     "harold denied ability to run cluster subject access review",
470
-		clientInterface: haroldClient.ClusterSubjectAccessReviews(),
471
-		review:          askCanClusterAdminsCreateProject,
472
-		err:             `User "harold" cannot create subjectaccessreviews at the cluster scope`,
469
+		description:      "harold denied ability to run cluster subject access review",
470
+		clusterInterface: haroldClient.SubjectAccessReviews(),
471
+		clusterReview:    askCanClusterAdminsCreateProject,
472
+		err:              `User "harold" cannot create subjectaccessreviews at the cluster scope`,
473 473
 	}.run(t)
474 474
 
475
-	askCanICreatePods := &authorizationapi.SubjectAccessReview{Verb: "create", Resource: "pods"}
475
+	askCanICreatePods := &authorizationapi.LocalSubjectAccessReview{
476
+		Action: authorizationapi.AuthorizationAttributes{Verb: "create", Resource: "pods"},
477
+	}
476 478
 	subjectAccessReviewTest{
477
-		description:     "harold told he can create pods in project hammer-project",
478
-		clientInterface: haroldClient.SubjectAccessReviews("hammer-project"),
479
-		review:          askCanICreatePods,
479
+		description:    "harold told he can create pods in project hammer-project",
480
+		localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"),
481
+		localReview:    askCanICreatePods,
480 482
 		response: authorizationapi.SubjectAccessReviewResponse{
481 483
 			Allowed:   true,
482 484
 			Reason:    "allowed by rule in hammer-project",
483 485
 			Namespace: "hammer-project",
484 486
 		},
485 487
 	}.run(t)
486
-	askCanICreatePolicyBindings := &authorizationapi.SubjectAccessReview{Verb: "create", Resource: "policybindings"}
488
+	askCanICreatePolicyBindings := &authorizationapi.LocalSubjectAccessReview{
489
+		Action: authorizationapi.AuthorizationAttributes{Verb: "create", Resource: "policybindings"},
490
+	}
487 491
 	subjectAccessReviewTest{
488
-		description:     "harold told he can create policybindings in project hammer-project",
489
-		clientInterface: haroldClient.SubjectAccessReviews("hammer-project"),
490
-		review:          askCanICreatePolicyBindings,
492
+		description:    "harold told he can create policybindings in project hammer-project",
493
+		localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"),
494
+		localReview:    askCanICreatePolicyBindings,
491 495
 		response: authorizationapi.SubjectAccessReviewResponse{
492 496
 			Allowed:   false,
493 497
 			Reason:    `User "harold" cannot create policybindings in project "hammer-project"`,
... ...
@@ -496,3 +563,161 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) {
496 496
 	}.run(t)
497 497
 
498 498
 }
499
+
500
+// TestOldLocalSubjectAccessReviewEndpoint checks to make sure that the old subject access review endpoint still functions properly
501
+// this is needed to support old docker registry images
502
+func TestOldLocalSubjectAccessReviewEndpoint(t *testing.T) {
503
+	_, clusterAdminKubeConfig, err := testutil.StartTestMaster()
504
+	if err != nil {
505
+		t.Fatalf("unexpected error: %v", err)
506
+	}
507
+
508
+	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
509
+	if err != nil {
510
+		t.Fatalf("unexpected error: %v", err)
511
+	}
512
+
513
+	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
514
+	if err != nil {
515
+		t.Fatalf("unexpected error: %v", err)
516
+	}
517
+
518
+	haroldClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
519
+	if err != nil {
520
+		t.Fatalf("unexpected error: %v", err)
521
+	}
522
+
523
+	namespace := "hammer-project"
524
+
525
+	// simple check
526
+	{
527
+		sar := &authorizationapi.SubjectAccessReview{
528
+			Action: authorizationapi.AuthorizationAttributes{
529
+				Verb:     "get",
530
+				Resource: "imagestreams/layers",
531
+			},
532
+		}
533
+		actualResponse := &authorizationapi.SubjectAccessReviewResponse{}
534
+		err := haroldClient.Post().Namespace(namespace).Resource("subjectAccessReviews").Body(sar).Do().Into(actualResponse)
535
+		if err != nil {
536
+			t.Errorf("unexpected error: %v", err)
537
+		}
538
+
539
+		expectedResponse := &authorizationapi.SubjectAccessReviewResponse{
540
+			Allowed:   true,
541
+			Reason:    `allowed by rule in hammer-project`,
542
+			Namespace: namespace,
543
+		}
544
+		if (actualResponse.Namespace != expectedResponse.Namespace) ||
545
+			(actualResponse.Allowed != expectedResponse.Allowed) ||
546
+			(!strings.HasPrefix(actualResponse.Reason, expectedResponse.Reason)) {
547
+			t.Errorf("review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", sar, expectedResponse, actualResponse)
548
+		}
549
+	}
550
+
551
+	// namespace forced to allowed namespace so we can't trick the server into leaking
552
+	{
553
+		sar := &authorizationapi.SubjectAccessReview{
554
+			Action: authorizationapi.AuthorizationAttributes{
555
+				Namespace: "sneaky-user",
556
+				Verb:      "get",
557
+				Resource:  "imagestreams/layers",
558
+			},
559
+		}
560
+		actualResponse := &authorizationapi.SubjectAccessReviewResponse{}
561
+		err := haroldClient.Post().Namespace(namespace).Resource("subjectAccessReviews").Body(sar).Do().Into(actualResponse)
562
+		if err != nil {
563
+			t.Errorf("unexpected error: %v", err)
564
+		}
565
+
566
+		expectedResponse := &authorizationapi.SubjectAccessReviewResponse{
567
+			Allowed:   true,
568
+			Reason:    `allowed by rule in hammer-project`,
569
+			Namespace: namespace,
570
+		}
571
+		if (actualResponse.Namespace != expectedResponse.Namespace) ||
572
+			(actualResponse.Allowed != expectedResponse.Allowed) ||
573
+			(!strings.HasPrefix(actualResponse.Reason, expectedResponse.Reason)) {
574
+			t.Errorf("review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", sar, expectedResponse, actualResponse)
575
+		}
576
+	}
577
+}
578
+
579
+// TestOldLocalResourceAccessReviewEndpoint checks to make sure that the old resource access review endpoint still functions properly
580
+// this is needed to support old who-can client
581
+func TestOldLocalResourceAccessReviewEndpoint(t *testing.T) {
582
+	_, clusterAdminKubeConfig, err := testutil.StartTestMaster()
583
+	if err != nil {
584
+		t.Fatalf("unexpected error: %v", err)
585
+	}
586
+
587
+	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
588
+	if err != nil {
589
+		t.Fatalf("unexpected error: %v", err)
590
+	}
591
+
592
+	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
593
+	if err != nil {
594
+		t.Fatalf("unexpected error: %v", err)
595
+	}
596
+
597
+	haroldClient, err := testutil.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold")
598
+	if err != nil {
599
+		t.Fatalf("unexpected error: %v", err)
600
+	}
601
+
602
+	namespace := "hammer-project"
603
+
604
+	// simple check
605
+	{
606
+		rar := &authorizationapi.ResourceAccessReview{
607
+			Action: authorizationapi.AuthorizationAttributes{
608
+				Verb:     "get",
609
+				Resource: "imagestreams/layers",
610
+			},
611
+		}
612
+		actualResponse := &authorizationapi.ResourceAccessReviewResponse{}
613
+		err := haroldClient.Post().Namespace(namespace).Resource("resourceAccessReviews").Body(rar).Do().Into(actualResponse)
614
+		if err != nil {
615
+			t.Errorf("unexpected error: %v", err)
616
+		}
617
+
618
+		expectedResponse := &authorizationapi.ResourceAccessReviewResponse{
619
+			Namespace: namespace,
620
+			Users:     util.NewStringSet("harold", "system:serviceaccount:hammer-project:builder"),
621
+			Groups:    util.NewStringSet("system:cluster-admins", "system:masters", "system:serviceaccounts:hammer-project"),
622
+		}
623
+		if (actualResponse.Namespace != expectedResponse.Namespace) ||
624
+			!reflect.DeepEqual(actualResponse.Users.List(), expectedResponse.Users.List()) ||
625
+			!reflect.DeepEqual(actualResponse.Groups.List(), expectedResponse.Groups.List()) {
626
+			t.Errorf("review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", rar, expectedResponse, actualResponse)
627
+		}
628
+	}
629
+
630
+	// namespace forced to allowed namespace so we can't trick the server into leaking
631
+	{
632
+		rar := &authorizationapi.ResourceAccessReview{
633
+			Action: authorizationapi.AuthorizationAttributes{
634
+				Namespace: "sneaky-user",
635
+				Verb:      "get",
636
+				Resource:  "imagestreams/layers",
637
+			},
638
+		}
639
+		actualResponse := &authorizationapi.ResourceAccessReviewResponse{}
640
+		err := haroldClient.Post().Namespace(namespace).Resource("resourceAccessReviews").Body(rar).Do().Into(actualResponse)
641
+		if err != nil {
642
+			t.Errorf("unexpected error: %v", err)
643
+		}
644
+
645
+		expectedResponse := &authorizationapi.ResourceAccessReviewResponse{
646
+			Namespace: namespace,
647
+			Users:     util.NewStringSet("harold", "system:serviceaccount:hammer-project:builder"),
648
+			Groups:    util.NewStringSet("system:cluster-admins", "system:masters", "system:serviceaccounts:hammer-project"),
649
+		}
650
+		if (actualResponse.Namespace != expectedResponse.Namespace) ||
651
+			!reflect.DeepEqual(actualResponse.Users.List(), expectedResponse.Users.List()) ||
652
+			!reflect.DeepEqual(actualResponse.Groups.List(), expectedResponse.Groups.List()) {
653
+			t.Errorf("review\n\t%#v\nexpected\n\t%#v\ngot\n\t%#v", rar, expectedResponse, actualResponse)
654
+		}
655
+	}
656
+}
... ...
@@ -159,10 +159,12 @@ func TestBootstrapPolicySelfSubjectAccessReviews(t *testing.T) {
159 159
 	}
160 160
 
161 161
 	// can I get a subjectaccessreview on myself even if I have no rights to do it generally
162
-	askCanICreatePolicyBindings := &authorizationapi.SubjectAccessReview{Verb: "create", Resource: "policybindings"}
162
+	askCanICreatePolicyBindings := &authorizationapi.LocalSubjectAccessReview{
163
+		Action: authorizationapi.AuthorizationAttributes{Verb: "create", Resource: "policybindings"},
164
+	}
163 165
 	subjectAccessReviewTest{
164
-		clientInterface: valerieOpenshiftClient.SubjectAccessReviews("openshift"),
165
-		review:          askCanICreatePolicyBindings,
166
+		localInterface: valerieOpenshiftClient.LocalSubjectAccessReviews("openshift"),
167
+		localReview:    askCanICreatePolicyBindings,
166 168
 		response: authorizationapi.SubjectAccessReviewResponse{
167 169
 			Allowed:   false,
168 170
 			Reason:    `User "valerie" cannot create policybindings in project "openshift"`,
... ...
@@ -171,11 +173,14 @@ func TestBootstrapPolicySelfSubjectAccessReviews(t *testing.T) {
171 171
 	}.run(t)
172 172
 
173 173
 	// I shouldn't be allowed to ask whether someone else can perform an action
174
-	askCanClusterAdminsCreateProject := &authorizationapi.SubjectAccessReview{Groups: util.NewStringSet("system:cluster-admins"), Verb: "create", Resource: "projects"}
174
+	askCanClusterAdminsCreateProject := &authorizationapi.LocalSubjectAccessReview{
175
+		Groups: util.NewStringSet("system:cluster-admins"),
176
+		Action: authorizationapi.AuthorizationAttributes{Verb: "create", Resource: "projects"},
177
+	}
175 178
 	subjectAccessReviewTest{
176
-		clientInterface: valerieOpenshiftClient.SubjectAccessReviews("openshift"),
177
-		review:          askCanClusterAdminsCreateProject,
178
-		err:             `User "valerie" cannot create subjectaccessreviews in project "openshift"`,
179
+		localInterface: valerieOpenshiftClient.LocalSubjectAccessReviews("openshift"),
180
+		localReview:    askCanClusterAdminsCreateProject,
181
+		err:            `User "valerie" cannot create localsubjectaccessreviews in project "openshift"`,
179 182
 	}.run(t)
180 183
 
181 184
 }
... ...
@@ -17,9 +17,9 @@ const (
17 17
 // WaitForPolicyUpdate checks if the given client can perform the named verb and action.
18 18
 // If PolicyCachePollTimeout is reached without the expected condition matching, an error is returned
19 19
 func WaitForPolicyUpdate(c *client.Client, namespace, verb, resource string, allowed bool) error {
20
-	review := &authorizationapi.SubjectAccessReview{Verb: verb, Resource: resource}
20
+	review := &authorizationapi.LocalSubjectAccessReview{Action: authorizationapi.AuthorizationAttributes{Verb: verb, Resource: resource}}
21 21
 	err := wait.Poll(PolicyCachePollInterval, PolicyCachePollTimeout, func() (bool, error) {
22
-		response, err := c.SubjectAccessReviews(namespace).Create(review)
22
+		response, err := c.LocalSubjectAccessReviews(namespace).Create(review)
23 23
 		if err != nil {
24 24
 			return false, err
25 25
 		}