forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
grantchecker.go
87 lines (72 loc) · 3.79 KB
/
grantchecker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package registry
import (
stderrors "errors"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kuser "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/client-go/util/retry"
"github.com/openshift/origin/pkg/auth/api"
"github.com/openshift/origin/pkg/oauth/apis/oauth"
oauthclient "github.com/openshift/origin/pkg/oauth/generated/internalclientset/typed/oauth/internalversion"
"github.com/openshift/origin/pkg/oauth/registry/oauthclientauthorization"
"github.com/openshift/origin/pkg/oauth/scope"
"github.com/golang/glog"
)
var errEmptyUID = stderrors.New("user from request has empty UID and thus cannot perform a grant flow")
type ClientAuthorizationGrantChecker struct {
client oauthclient.OAuthClientAuthorizationInterface
}
func NewClientAuthorizationGrantChecker(client oauthclient.OAuthClientAuthorizationInterface) *ClientAuthorizationGrantChecker {
return &ClientAuthorizationGrantChecker{client}
}
func (c *ClientAuthorizationGrantChecker) HasAuthorizedClient(user kuser.Info, grant *api.Grant) (approved bool, err error) {
// Validation prevents OAuthClientAuthorization.UserUID from being empty (and always has).
// However, user.GetUID() is empty during impersonation, meaning this flow does not work for impersonation.
// This is fine because no OAuth / grant flow works with impersonation in general.
if len(user.GetUID()) == 0 {
return false, errEmptyUID
}
id := oauthclientauthorization.ClientAuthorizationName(user.GetName(), grant.Client.GetId())
var authorization *oauth.OAuthClientAuthorization
// getClientAuthorization ignores not found errors, thus it is possible for authorization to be nil
// getClientAuthorization does not ignore conflict errors, so we retry those in case we having multiple clients racing on this grant flow
// all other errors are considered fatal
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
authorization, err = c.getClientAuthorization(id, user)
return err
}); err != nil {
return false, err
}
// TODO: improve this to allow the scope implementation to determine overlap
if authorization == nil || !scope.Covers(authorization.Scopes, scope.Split(grant.Scope)) {
return false, nil
}
return true, nil
}
// getClientAuthorization gets the OAuthClientAuthorization with the given name and validates that it matches the given user
// it attempts to delete stale client authorizations, and thus must be retried in case of conflicts
func (c *ClientAuthorizationGrantChecker) getClientAuthorization(name string, user kuser.Info) (*oauth.OAuthClientAuthorization, error) {
authorization, err := c.client.Get(name, metav1.GetOptions{})
if errors.IsNotFound(err) {
// if no such authorization exists, it simply means the user needs to go through the grant flow
return nil, nil
}
if err != nil {
// any other error is fatal (this will never be retried since it cannot return a conflict error)
return nil, err
}
// check to see if we have a stale authorization
// user.GetUID() and authorization.UserUID are both guaranteed to be non-empty
if user.GetUID() != authorization.UserUID {
glog.Infof("%#v does not match stored client authorization %#v, attempting to delete stale authorization", user, authorization)
if err := c.client.Delete(name, metav1.NewPreconditionDeleteOptions(string(authorization.UID))); err != nil && !errors.IsNotFound(err) {
// ignore not found since that could be caused by multiple grant flows occurring at once (the other flow deleted the authorization before we did)
// this could be a conflict error, which will cause this whole function to be retried
return nil, err
}
// we successfully deleted the authorization so the user needs to go through the grant flow
return nil, nil
}
// everything looks good so we can return the authorization
return authorization, nil
}