1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,24 @@ |
0 |
+package handlers |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "net/http" |
|
4 |
+ |
|
5 |
+ oauthhandlers "github.com/openshift/origin/pkg/auth/oauth/handlers" |
|
6 |
+) |
|
7 |
+ |
|
8 |
+type basicPasswordAuthHandler struct { |
|
9 |
+ realm string |
|
10 |
+} |
|
11 |
+ |
|
12 |
+// NewBasicPasswordAuthHandler returns a ChallengeAuthHandler that responds with a basic auth challenge for the supplied realm |
|
13 |
+func NewBasicAuthChallenger(realm string) oauthhandlers.AuthenticationChallenger { |
|
14 |
+ return &basicPasswordAuthHandler{realm} |
|
15 |
+} |
|
16 |
+ |
|
17 |
+// AuthenticationChallengeNeeded returns a header that indicates a basic auth challenge for the supplied realm |
|
18 |
+func (h *basicPasswordAuthHandler) AuthenticationChallenge(req *http.Request) (http.Header, error) { |
|
19 |
+ headers := http.Header{} |
|
20 |
+ headers.Add("WWW-Authenticate", "Basic realm=\""+h.realm+"\"") |
|
21 |
+ |
|
22 |
+ return headers, nil |
|
23 |
+} |
0 | 24 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,27 @@ |
0 |
+package handlers |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "net/http" |
|
4 |
+ "testing" |
|
5 |
+) |
|
6 |
+ |
|
7 |
+func TestAuthChallengeNeeded(t *testing.T) { |
|
8 |
+ handler := NewBasicAuthChallenger("testing-realm") |
|
9 |
+ req := &http.Request{} |
|
10 |
+ header, err := handler.AuthenticationChallenge(req) |
|
11 |
+ |
|
12 |
+ if err != nil { |
|
13 |
+ t.Errorf("Unexepcted error: %v", err) |
|
14 |
+ } |
|
15 |
+ |
|
16 |
+ if value, ok := header["Www-Authenticate"]; ok { |
|
17 |
+ expectedValue := "Basic realm=\"testing-realm\"" |
|
18 |
+ if value[0] != expectedValue { |
|
19 |
+ t.Errorf("Expected %v, got %v", expectedValue, value) |
|
20 |
+ } |
|
21 |
+ } else { |
|
22 |
+ t.Error("Did not get back header") |
|
23 |
+ |
|
24 |
+ } |
|
25 |
+ |
|
26 |
+} |
... | ... |
@@ -9,13 +9,17 @@ import ( |
9 | 9 |
"github.com/openshift/origin/pkg/auth/authenticator" |
10 | 10 |
) |
11 | 11 |
|
12 |
+// TODO remove this in favor of kubernetes types |
|
13 |
+ |
|
12 | 14 |
type unionAuthRequestHandler []authenticator.Request |
13 | 15 |
|
16 |
+// NewUnionAuthentication returns a request authenticator that validates credentials using a chain of authenticator.Request objects |
|
14 | 17 |
func NewUnionAuthentication(authRequestHandlers []authenticator.Request) authenticator.Request { |
15 |
- ret := unionAuthRequestHandler(authRequestHandlers) |
|
16 |
- return &ret |
|
18 |
+ return unionAuthRequestHandler(authRequestHandlers) |
|
17 | 19 |
} |
18 | 20 |
|
21 |
+// AuthenticateRequest authenticates the request using a chain of authenticator.Request objects. The first |
|
22 |
+// success returns that identity. Errors are only returned if no matches are found. |
|
19 | 23 |
func (authHandler unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (authapi.UserInfo, bool, error) { |
20 | 24 |
var errors kutil.ErrorList |
21 | 25 |
for _, currAuthRequestHandler := range authHandler { |
... | ... |
@@ -26,7 +26,7 @@ type Handler struct { |
26 | 26 |
mapper authapi.UserIdentityMapper |
27 | 27 |
} |
28 | 28 |
|
29 |
-func NewHandler(provider Provider, state State, redirectURL string, success handlers.AuthenticationSuccessHandler, errorHandler handlers.AuthenticationErrorHandler, mapper authapi.UserIdentityMapper) (*Handler, error) { |
|
29 |
+func NewExternalOAuthRedirector(provider Provider, state State, redirectURL string, success handlers.AuthenticationSuccessHandler, errorHandler handlers.AuthenticationErrorHandler, mapper authapi.UserIdentityMapper) (*Handler, error) { |
|
30 | 30 |
clientConfig, err := provider.NewConfig() |
31 | 31 |
if err != nil { |
32 | 32 |
return nil, err |
... | ... |
@@ -50,8 +50,8 @@ func NewHandler(provider Provider, state State, redirectURL string, success hand |
50 | 50 |
}, nil |
51 | 51 |
} |
52 | 52 |
|
53 |
-// Implements oauth.handlers.AuthenticationHandler |
|
54 |
-func (h *Handler) AuthenticationNeeded(w http.ResponseWriter, req *http.Request) (bool, error) { |
|
53 |
+// Implements oauth.handlers.RedirectAuthHandler |
|
54 |
+func (h *Handler) AuthenticationRedirect(w http.ResponseWriter, req *http.Request) error { |
|
55 | 55 |
glog.V(4).Infof("Authentication needed for %v", h) |
56 | 56 |
|
57 | 57 |
authReq := h.client.NewAuthorizeRequest(osincli.CODE) |
... | ... |
@@ -60,14 +60,14 @@ func (h *Handler) AuthenticationNeeded(w http.ResponseWriter, req *http.Request) |
60 | 60 |
state, err := h.state.Generate(w, req) |
61 | 61 |
if err != nil { |
62 | 62 |
glog.V(4).Infof("Error generating state: %v", err) |
63 |
- return h.errorHandler.AuthenticationError(err, w, req) |
|
63 |
+ return err |
|
64 | 64 |
} |
65 | 65 |
|
66 | 66 |
oauthURL := authReq.GetAuthorizeUrlWithParams(state) |
67 | 67 |
glog.V(4).Infof("redirect to %v", oauthURL) |
68 | 68 |
|
69 | 69 |
http.Redirect(w, req, oauthURL.String(), http.StatusFound) |
70 |
- return true, nil |
|
70 |
+ return nil |
|
71 | 71 |
} |
72 | 72 |
|
73 | 73 |
// Handles the callback request in response to an external oauth flow |
... | ... |
@@ -31,7 +31,7 @@ func (h *AuthorizeAuthenticator) HandleAuthorize(ar *osin.AuthorizeRequest, w ht |
31 | 31 |
return h.errorHandler.AuthenticationError(err, w, ar.HttpRequest) |
32 | 32 |
} |
33 | 33 |
if !ok { |
34 |
- return h.handler.AuthenticationNeeded(w, ar.HttpRequest) |
|
34 |
+ return h.handler.AuthenticationNeeded(ar.Client, w, ar.HttpRequest) |
|
35 | 35 |
} |
36 | 36 |
ar.UserData = info |
37 | 37 |
ar.Authorized = true |
38 | 38 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,112 @@ |
0 |
+package handlers |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "net/http" |
|
5 |
+ |
|
6 |
+ kutil "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
7 |
+ |
|
8 |
+ authapi "github.com/openshift/origin/pkg/auth/api" |
|
9 |
+ oauthapi "github.com/openshift/origin/pkg/oauth/api" |
|
10 |
+) |
|
11 |
+ |
|
12 |
+// unionAuthenticationHandler is an oauth.AuthenticationHandler that muxes multiple challange handlers and redirect handlers |
|
13 |
+type unionAuthenticationHandler struct { |
|
14 |
+ challengers map[string]AuthenticationChallenger |
|
15 |
+ redirectors map[string]AuthenticationRedirector |
|
16 |
+ errorHandler AuthenticationErrorHandler |
|
17 |
+} |
|
18 |
+ |
|
19 |
+// NewUnionAuthenticationHandler returns an oauth.AuthenticationHandler that muxes multiple challange handlers and redirect handlers |
|
20 |
+func NewUnionAuthenticationHandler(passedChallengers map[string]AuthenticationChallenger, passedRedirectors map[string]AuthenticationRedirector, errorHandler AuthenticationErrorHandler) AuthenticationHandler { |
|
21 |
+ challengers := passedChallengers |
|
22 |
+ if challengers == nil { |
|
23 |
+ challengers = make(map[string]AuthenticationChallenger, 1) |
|
24 |
+ } |
|
25 |
+ |
|
26 |
+ redirectors := passedRedirectors |
|
27 |
+ if redirectors == nil { |
|
28 |
+ redirectors = make(map[string]AuthenticationRedirector, 1) |
|
29 |
+ } |
|
30 |
+ |
|
31 |
+ return &unionAuthenticationHandler{challengers, redirectors, errorHandler} |
|
32 |
+} |
|
33 |
+ |
|
34 |
+// AuthenticationNeeded looks at the oauth Client to determine whether it wants try to authenticate with challenges or using a redirect path |
|
35 |
+// If the client wants a challenge path, it muxes together all the different challenges from the challenge handlers |
|
36 |
+// If (the client wants a redirect path) and ((there is one redirect handler) or (a redirect handler was requested via the "useRedirectHandler" parameter), |
|
37 |
+// then the redirect handler is called. Otherwise, you get an error (currently) or a redirect to a page letting you choose how you'd like to authenticate. |
|
38 |
+// It returns whether the response was written and/or an error |
|
39 |
+func (authHandler *unionAuthenticationHandler) AuthenticationNeeded(apiClient authapi.Client, w http.ResponseWriter, req *http.Request) (bool, error) { |
|
40 |
+ client, ok := apiClient.GetUserData().(*oauthapi.Client) |
|
41 |
+ if !ok { |
|
42 |
+ return false, fmt.Errorf("apiClient data was not an oauthapi.Client") |
|
43 |
+ } |
|
44 |
+ |
|
45 |
+ if client.RespondWithChallenges { |
|
46 |
+ var errors kutil.ErrorList |
|
47 |
+ headers := http.Header(make(map[string][]string)) |
|
48 |
+ for _, challengingHandler := range authHandler.challengers { |
|
49 |
+ currHeaders, err := challengingHandler.AuthenticationChallenge(req) |
|
50 |
+ if err != nil { |
|
51 |
+ errors = append(errors, err) |
|
52 |
+ continue |
|
53 |
+ } |
|
54 |
+ |
|
55 |
+ // merge header values |
|
56 |
+ mergeHeaders(headers, currHeaders) |
|
57 |
+ } |
|
58 |
+ |
|
59 |
+ if len(headers) > 0 { |
|
60 |
+ mergeHeaders(w.Header(), headers) |
|
61 |
+ w.WriteHeader(http.StatusUnauthorized) |
|
62 |
+ return true, nil |
|
63 |
+ |
|
64 |
+ } else { |
|
65 |
+ return false, errors.ToError() |
|
66 |
+ } |
|
67 |
+ |
|
68 |
+ } else { |
|
69 |
+ redirectHandlerName := req.URL.Query().Get("useRedirectHandler") |
|
70 |
+ |
|
71 |
+ if len(redirectHandlerName) > 0 { |
|
72 |
+ redirectHandler := authHandler.redirectors[redirectHandlerName] |
|
73 |
+ if redirectHandler == nil { |
|
74 |
+ return false, fmt.Errorf("Unable to locate redirect handler: %v", redirectHandlerName) |
|
75 |
+ } |
|
76 |
+ |
|
77 |
+ err := redirectHandler.AuthenticationRedirect(w, req) |
|
78 |
+ if err != nil { |
|
79 |
+ return authHandler.errorHandler.AuthenticationError(err, w, req) |
|
80 |
+ } |
|
81 |
+ return true, nil |
|
82 |
+ |
|
83 |
+ } else { |
|
84 |
+ if (len(authHandler.redirectors)) == 1 { |
|
85 |
+ // there has to be a better way |
|
86 |
+ for _, redirectHandler := range authHandler.redirectors { |
|
87 |
+ err := redirectHandler.AuthenticationRedirect(w, req) |
|
88 |
+ if err != nil { |
|
89 |
+ return authHandler.errorHandler.AuthenticationError(err, w, req) |
|
90 |
+ } |
|
91 |
+ return true, nil |
|
92 |
+ } |
|
93 |
+ } else if len(authHandler.redirectors) > 1 { |
|
94 |
+ // TODO this clearly doesn't work right. There should probably be a redirect to an interstitial page. |
|
95 |
+ // however, this is just as good as we have now. |
|
96 |
+ return false, fmt.Errorf("Too many potential redirect handlers: %v", authHandler.redirectors) |
|
97 |
+ } |
|
98 |
+ |
|
99 |
+ } |
|
100 |
+ } |
|
101 |
+ |
|
102 |
+ return false, nil |
|
103 |
+} |
|
104 |
+ |
|
105 |
+func mergeHeaders(dest http.Header, toAdd http.Header) { |
|
106 |
+ for key, values := range toAdd { |
|
107 |
+ for _, value := range values { |
|
108 |
+ dest.Add(key, value) |
|
109 |
+ } |
|
110 |
+ } |
|
111 |
+} |
0 | 112 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,181 @@ |
0 |
+package handlers |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "errors" |
|
4 |
+ "net/http" |
|
5 |
+ "net/http/httptest" |
|
6 |
+ "reflect" |
|
7 |
+ "strings" |
|
8 |
+ "testing" |
|
9 |
+ |
|
10 |
+ oauthapi "github.com/openshift/origin/pkg/oauth/api" |
|
11 |
+) |
|
12 |
+ |
|
13 |
+type testClient struct { |
|
14 |
+ client *oauthapi.Client |
|
15 |
+} |
|
16 |
+ |
|
17 |
+func (w *testClient) GetId() string { |
|
18 |
+ return w.client.Name |
|
19 |
+} |
|
20 |
+func (w *testClient) GetSecret() string { |
|
21 |
+ return w.client.Secret |
|
22 |
+} |
|
23 |
+func (w *testClient) GetRedirectUri() string { |
|
24 |
+ if len(w.client.RedirectURIs) == 0 { |
|
25 |
+ return "" |
|
26 |
+ } |
|
27 |
+ return strings.Join(w.client.RedirectURIs, ",") |
|
28 |
+} |
|
29 |
+func (w *testClient) GetUserData() interface{} { |
|
30 |
+ return w.client |
|
31 |
+} |
|
32 |
+ |
|
33 |
+type mockChallenger struct { |
|
34 |
+ headerName string |
|
35 |
+ headerValue string |
|
36 |
+ err error |
|
37 |
+} |
|
38 |
+ |
|
39 |
+func (h *mockChallenger) AuthenticationChallenge(req *http.Request) (http.Header, error) { |
|
40 |
+ headers := http.Header{} |
|
41 |
+ if len(h.headerName) > 0 { |
|
42 |
+ headers.Add(h.headerName, h.headerValue) |
|
43 |
+ } |
|
44 |
+ |
|
45 |
+ return headers, h.err |
|
46 |
+} |
|
47 |
+ |
|
48 |
+func TestNoHandlersRedirect(t *testing.T) { |
|
49 |
+ authHandler := NewUnionAuthenticationHandler(nil, nil, nil) |
|
50 |
+ client := &testClient{&oauthapi.Client{}} |
|
51 |
+ req, _ := http.NewRequest("GET", "http://example.org", nil) |
|
52 |
+ responseRecorder := httptest.NewRecorder() |
|
53 |
+ handled, err := authHandler.AuthenticationNeeded(client, responseRecorder, req) |
|
54 |
+ |
|
55 |
+ if err != nil { |
|
56 |
+ t.Errorf("Unexepcted error: %v", err) |
|
57 |
+ } |
|
58 |
+ |
|
59 |
+ if handled { |
|
60 |
+ t.Error("Unexpectedly handled.") |
|
61 |
+ } |
|
62 |
+} |
|
63 |
+ |
|
64 |
+func TestNoHandlersChallenge(t *testing.T) { |
|
65 |
+ authHandler := NewUnionAuthenticationHandler(nil, nil, nil) |
|
66 |
+ client := &testClient{&oauthapi.Client{RespondWithChallenges: true}} |
|
67 |
+ req, _ := http.NewRequest("GET", "http://example.org", nil) |
|
68 |
+ responseRecorder := httptest.NewRecorder() |
|
69 |
+ handled, err := authHandler.AuthenticationNeeded(client, responseRecorder, req) |
|
70 |
+ |
|
71 |
+ if err != nil { |
|
72 |
+ t.Errorf("Unexepcted error: %v", err) |
|
73 |
+ } |
|
74 |
+ |
|
75 |
+ if handled { |
|
76 |
+ t.Error("Unexpectedly handled.") |
|
77 |
+ } |
|
78 |
+} |
|
79 |
+ |
|
80 |
+func TestWithBadClient(t *testing.T) { |
|
81 |
+ authHandler := NewUnionAuthenticationHandler(nil, nil, nil) |
|
82 |
+ client := &badTestClient{&oauthapi.Client{}} |
|
83 |
+ req, _ := http.NewRequest("GET", "http://example.org", nil) |
|
84 |
+ responseRecorder := httptest.NewRecorder() |
|
85 |
+ handled, err := authHandler.AuthenticationNeeded(client, responseRecorder, req) |
|
86 |
+ |
|
87 |
+ expectedError := "apiClient data was not an oauthapi.Client" |
|
88 |
+ if err == nil { |
|
89 |
+ t.Errorf("Expected error: %v", expectedError) |
|
90 |
+ } |
|
91 |
+ if err.Error() != expectedError { |
|
92 |
+ t.Errorf("Expected %v, got %v", expectedError, err) |
|
93 |
+ } |
|
94 |
+ |
|
95 |
+ if handled { |
|
96 |
+ t.Error("Unexpectedly handled.") |
|
97 |
+ } |
|
98 |
+} |
|
99 |
+ |
|
100 |
+func TestWithOnlyChallengeErrors(t *testing.T) { |
|
101 |
+ expectedError1 := "alfa" |
|
102 |
+ expectedError2 := "bravo" |
|
103 |
+ failingChallengeHandler1 := &mockChallenger{err: errors.New(expectedError1)} |
|
104 |
+ failingChallengeHandler2 := &mockChallenger{err: errors.New(expectedError2)} |
|
105 |
+ authHandler := NewUnionAuthenticationHandler( |
|
106 |
+ map[string]AuthenticationChallenger{"first": failingChallengeHandler1, "second": failingChallengeHandler2}, |
|
107 |
+ nil, nil) |
|
108 |
+ client := &testClient{&oauthapi.Client{RespondWithChallenges: true}} |
|
109 |
+ req, _ := http.NewRequest("GET", "http://example.org", nil) |
|
110 |
+ responseRecorder := httptest.NewRecorder() |
|
111 |
+ handled, err := authHandler.AuthenticationNeeded(client, responseRecorder, req) |
|
112 |
+ |
|
113 |
+ if err == nil { |
|
114 |
+ t.Errorf("Expected error: %v and %v", expectedError1, expectedError2) |
|
115 |
+ } |
|
116 |
+ if !strings.Contains(err.Error(), expectedError1) { |
|
117 |
+ t.Errorf("Expected %v, got %v", expectedError1, err) |
|
118 |
+ } |
|
119 |
+ if !strings.Contains(err.Error(), expectedError2) { |
|
120 |
+ t.Errorf("Expected %v, got %v", expectedError2, err) |
|
121 |
+ } |
|
122 |
+ |
|
123 |
+ if handled { |
|
124 |
+ t.Error("Unexpectedly handled.") |
|
125 |
+ } |
|
126 |
+} |
|
127 |
+ |
|
128 |
+func TestWithChallengeErrorsAndMergedSuccess(t *testing.T) { |
|
129 |
+ expectedError := "failure" |
|
130 |
+ failingChallengeHandler := &mockChallenger{err: errors.New(expectedError)} |
|
131 |
+ workingChallengeHandler1 := &mockChallenger{headerName: "Charlie", headerValue: "delta"} |
|
132 |
+ workingChallengeHandler2 := &mockChallenger{headerName: "Echo", headerValue: "foxtrot"} |
|
133 |
+ workingChallengeHandler3 := &mockChallenger{headerName: "Charlie", headerValue: "golf"} |
|
134 |
+ |
|
135 |
+ // order of the array is not guaranteed |
|
136 |
+ expectedHeader1 := map[string][]string{"Charlie": {"delta", "golf"}, "Echo": {"foxtrot"}} |
|
137 |
+ expectedHeader2 := map[string][]string{"Charlie": {"golf", "delta"}, "Echo": {"foxtrot"}} |
|
138 |
+ |
|
139 |
+ authHandler := NewUnionAuthenticationHandler( |
|
140 |
+ map[string]AuthenticationChallenger{ |
|
141 |
+ "first": failingChallengeHandler, |
|
142 |
+ "second": workingChallengeHandler1, |
|
143 |
+ "third": workingChallengeHandler2, |
|
144 |
+ "fourth": workingChallengeHandler3}, |
|
145 |
+ nil, nil) |
|
146 |
+ client := &testClient{&oauthapi.Client{RespondWithChallenges: true}} |
|
147 |
+ req, _ := http.NewRequest("GET", "http://example.org", nil) |
|
148 |
+ responseRecorder := httptest.NewRecorder() |
|
149 |
+ handled, err := authHandler.AuthenticationNeeded(client, responseRecorder, req) |
|
150 |
+ |
|
151 |
+ if err != nil { |
|
152 |
+ t.Errorf("Unexepcted error: %v", err) |
|
153 |
+ } |
|
154 |
+ if !handled { |
|
155 |
+ t.Error("Expected handling.") |
|
156 |
+ } |
|
157 |
+ if !(reflect.DeepEqual(map[string][]string(responseRecorder.HeaderMap), expectedHeader1) || reflect.DeepEqual(map[string][]string(responseRecorder.HeaderMap), expectedHeader2)) { |
|
158 |
+ t.Errorf("Expected %#v or %#v, got %#v.", expectedHeader1, expectedHeader2, responseRecorder.HeaderMap) |
|
159 |
+ } |
|
160 |
+} |
|
161 |
+ |
|
162 |
+type badTestClient struct { |
|
163 |
+ client *oauthapi.Client |
|
164 |
+} |
|
165 |
+ |
|
166 |
+func (w *badTestClient) GetId() string { |
|
167 |
+ return w.client.Name |
|
168 |
+} |
|
169 |
+func (w *badTestClient) GetSecret() string { |
|
170 |
+ return w.client.Secret |
|
171 |
+} |
|
172 |
+func (w *badTestClient) GetRedirectUri() string { |
|
173 |
+ if len(w.client.RedirectURIs) == 0 { |
|
174 |
+ return "" |
|
175 |
+ } |
|
176 |
+ return strings.Join(w.client.RedirectURIs, ",") |
|
177 |
+} |
|
178 |
+func (w *badTestClient) GetUserData() interface{} { |
|
179 |
+ return "w.client" |
|
180 |
+} |
... | ... |
@@ -5,18 +5,18 @@ import ( |
5 | 5 |
|
6 | 6 |
"github.com/golang/glog" |
7 | 7 |
|
8 |
- "github.com/openshift/origin/pkg/auth/api" |
|
8 |
+ authapi "github.com/openshift/origin/pkg/auth/api" |
|
9 | 9 |
) |
10 | 10 |
|
11 | 11 |
type EmptyAuth struct{} |
12 | 12 |
|
13 |
-func (EmptyAuth) AuthenticationNeeded(w http.ResponseWriter, req *http.Request) (bool, error) { |
|
13 |
+func (EmptyAuth) AuthenticationNeeded(client authapi.Client, w http.ResponseWriter, req *http.Request) (bool, error) { |
|
14 | 14 |
return false, nil |
15 | 15 |
} |
16 | 16 |
|
17 | 17 |
type EmptySuccess struct{} |
18 | 18 |
|
19 |
-func (EmptySuccess) AuthenticationSucceeded(user api.UserInfo, state string, w http.ResponseWriter, req *http.Request) (bool, error) { |
|
19 |
+func (EmptySuccess) AuthenticationSucceeded(user authapi.UserInfo, state string, w http.ResponseWriter, req *http.Request) (bool, error) { |
|
20 | 20 |
glog.V(4).Infof("AuthenticationSucceeded: %v (state=%s)", user, state) |
21 | 21 |
return false, nil |
22 | 22 |
} |
... | ... |
@@ -9,7 +9,19 @@ import ( |
9 | 9 |
// AuthenticationHandler reacts to unauthenticated requests |
10 | 10 |
type AuthenticationHandler interface { |
11 | 11 |
// AuthenticationNeeded reacts to unauthenticated requests, and returns true if the response was written, |
12 |
- AuthenticationNeeded(w http.ResponseWriter, req *http.Request) (handled bool, err error) |
|
12 |
+ AuthenticationNeeded(client api.Client, w http.ResponseWriter, req *http.Request) (handled bool, err error) |
|
13 |
+} |
|
14 |
+ |
|
15 |
+// AuthenticationChallenger reacts to unauthenticated requests with challenges |
|
16 |
+type AuthenticationChallenger interface { |
|
17 |
+ // AuthenticationChallenge take a request and return whatever challenge headers are appropriate. If none are appropriate, it should return an empty map, not nil. |
|
18 |
+ AuthenticationChallenge(req *http.Request) (header http.Header, err error) |
|
19 |
+} |
|
20 |
+ |
|
21 |
+// AuthenticationRedirector reacts to unauthenticated requests with redirects |
|
22 |
+type AuthenticationRedirector interface { |
|
23 |
+ // AuthenticationRedirect is expected to write a redirect to the ResponseWriter or to return an error. |
|
24 |
+ AuthenticationRedirect(w http.ResponseWriter, req *http.Request) (err error) |
|
13 | 25 |
} |
14 | 26 |
|
15 | 27 |
// AuthenticationErrorHandler reacts to authentication errors |
... | ... |
@@ -38,7 +38,7 @@ type testHandlers struct { |
38 | 38 |
HandledErr error |
39 | 39 |
} |
40 | 40 |
|
41 |
-func (h *testHandlers) AuthenticationNeeded(w http.ResponseWriter, req *http.Request) (bool, error) { |
|
41 |
+func (h *testHandlers) AuthenticationNeeded(client api.Client, w http.ResponseWriter, req *http.Request) (bool, error) { |
|
42 | 42 |
h.AuthNeed = true |
43 | 43 |
return h.AuthNeedHandled, h.AuthNeedErr |
44 | 44 |
} |
... | ... |
@@ -8,7 +8,6 @@ import ( |
8 | 8 |
|
9 | 9 |
"github.com/RangelReale/osincli" |
10 | 10 |
|
11 |
- "github.com/openshift/origin/pkg/auth/oauth/handlers" |
|
12 | 11 |
"github.com/openshift/origin/pkg/auth/server/login" |
13 | 12 |
) |
14 | 13 |
|
... | ... |
@@ -18,7 +17,6 @@ const ( |
18 | 18 |
) |
19 | 19 |
|
20 | 20 |
type endpointDetails struct { |
21 |
- authHandler handlers.AuthenticationHandler |
|
22 | 21 |
// Jordan has done the osincli review and declares the osinclit.Client reusable and thread-safe |
23 | 22 |
originOAuthClient *osincli.Client |
24 | 23 |
} |
... | ... |
@@ -27,8 +25,8 @@ type Endpoints interface { |
27 | 27 |
Install(mux login.Mux, paths ...string) |
28 | 28 |
} |
29 | 29 |
|
30 |
-func NewEndpoints(authHandler handlers.AuthenticationHandler, originOAuthClient *osincli.Client) Endpoints { |
|
31 |
- return &endpointDetails{authHandler, originOAuthClient} |
|
30 |
+func NewEndpoints(originOAuthClient *osincli.Client) Endpoints { |
|
31 |
+ return &endpointDetails{originOAuthClient} |
|
32 | 32 |
} |
33 | 33 |
|
34 | 34 |
// Install registers the request token endpoints into a mux. It is expected that the |
35 | 35 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,29 @@ |
0 |
+package tokens |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "os" |
|
5 |
+ |
|
6 |
+ "github.com/spf13/cobra" |
|
7 |
+ |
|
8 |
+ "github.com/openshift/origin/pkg/cmd/util/clientcmd" |
|
9 |
+ "github.com/openshift/origin/pkg/cmd/util/tokencmd" |
|
10 |
+) |
|
11 |
+ |
|
12 |
+func NewCmdRequestToken(clientCfg *clientcmd.Config) *cobra.Command { |
|
13 |
+ cmd := &cobra.Command{ |
|
14 |
+ Use: "request-token", |
|
15 |
+ Short: "request an access token", |
|
16 |
+ Long: `request an access token`, |
|
17 |
+ Run: func(cmd *cobra.Command, args []string) { |
|
18 |
+ accessToken, err := tokencmd.RequestToken(clientCfg, os.Stdin) |
|
19 |
+ if err != nil { |
|
20 |
+ fmt.Printf("%v\n", err) |
|
21 |
+ return |
|
22 |
+ } |
|
23 |
+ |
|
24 |
+ fmt.Printf("%v\n", string(accessToken)) |
|
25 |
+ }, |
|
26 |
+ } |
|
27 |
+ return cmd |
|
28 |
+} |
0 | 29 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,54 @@ |
0 |
+package tokens |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "github.com/golang/glog" |
|
4 |
+ "github.com/spf13/cobra" |
|
5 |
+ |
|
6 |
+ "github.com/openshift/origin/pkg/auth/server/tokenrequest" |
|
7 |
+ "github.com/openshift/origin/pkg/cmd/server/origin" |
|
8 |
+ "github.com/openshift/origin/pkg/cmd/util/clientcmd" |
|
9 |
+) |
|
10 |
+ |
|
11 |
+const ( |
|
12 |
+ TOKEN_FILE_PARAM = "token-file" |
|
13 |
+) |
|
14 |
+ |
|
15 |
+func NewCommandTokens(name string) *cobra.Command { |
|
16 |
+ // Parent command to which all subcommands are added. |
|
17 |
+ cmds := &cobra.Command{ |
|
18 |
+ Use: name, |
|
19 |
+ Short: "manage authentication tokens", |
|
20 |
+ Long: `manage authentication tokens`, |
|
21 |
+ Run: runHelp, |
|
22 |
+ } |
|
23 |
+ |
|
24 |
+ // copied out of kubernetes kubectl so that I'll be ready when that and osc finally merge in |
|
25 |
+ // Globally persistent flags across all subcommands. |
|
26 |
+ // TODO Change flag names to consts to allow safer lookup from subcommands. |
|
27 |
+ // TODO Add a verbose flag that turns on glog logging. Probably need a way |
|
28 |
+ // to do that automatically for every subcommand. |
|
29 |
+ clientCfg := clientcmd.NewConfig() |
|
30 |
+ clientCfg.Bind(cmds.PersistentFlags()) |
|
31 |
+ |
|
32 |
+ cmds.AddCommand(NewCmdValidateToken(clientCfg)) |
|
33 |
+ cmds.AddCommand(NewCmdRequestToken(clientCfg)) |
|
34 |
+ cmds.AddCommand(NewCmdWhoAmI(clientCfg)) |
|
35 |
+ |
|
36 |
+ return cmds |
|
37 |
+} |
|
38 |
+ |
|
39 |
+func runHelp(cmd *cobra.Command, args []string) { |
|
40 |
+ cmd.Help() |
|
41 |
+} |
|
42 |
+ |
|
43 |
+func getFlagString(cmd *cobra.Command, flag string) string { |
|
44 |
+ f := cmd.Flags().Lookup(flag) |
|
45 |
+ if f == nil { |
|
46 |
+ glog.Fatalf("Flag accessed but not defined for command %s: %s", cmd.Name(), flag) |
|
47 |
+ } |
|
48 |
+ return f.Value.String() |
|
49 |
+} |
|
50 |
+ |
|
51 |
+func getRequestTokenUrl(clientCfg *clientcmd.Config) string { |
|
52 |
+ return clientCfg.KubeConfig().Host + origin.OpenShiftLoginPrefix + tokenrequest.RequestTokenEndpoint |
|
53 |
+} |
0 | 54 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,92 @@ |
0 |
+package tokens |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "encoding/json" |
|
4 |
+ "fmt" |
|
5 |
+ |
|
6 |
+ "github.com/golang/glog" |
|
7 |
+ "github.com/spf13/cobra" |
|
8 |
+ |
|
9 |
+ osclient "github.com/openshift/origin/pkg/client" |
|
10 |
+ "github.com/openshift/origin/pkg/cmd/util/clientcmd" |
|
11 |
+ "github.com/openshift/origin/pkg/oauth/osintypes" |
|
12 |
+) |
|
13 |
+ |
|
14 |
+func NewCmdValidateToken(clientCfg *clientcmd.Config) *cobra.Command { |
|
15 |
+ cmd := &cobra.Command{ |
|
16 |
+ Use: "validate-token", |
|
17 |
+ Short: "validate an access token", |
|
18 |
+ Long: `validate an access token`, |
|
19 |
+ Run: func(cmd *cobra.Command, args []string) { |
|
20 |
+ tokenValue := getFlagString(cmd, "token") |
|
21 |
+ validateToken(tokenValue, clientCfg) |
|
22 |
+ }, |
|
23 |
+ } |
|
24 |
+ cmd.Flags().String("token", "", "Token value") |
|
25 |
+ return cmd |
|
26 |
+} |
|
27 |
+ |
|
28 |
+func validateToken(token string, clientCfg *clientcmd.Config) { |
|
29 |
+ if len(token) == 0 { |
|
30 |
+ fmt.Println("You must provide a token to validate") |
|
31 |
+ return |
|
32 |
+ } |
|
33 |
+ fmt.Printf("Using token: %v\n", token) |
|
34 |
+ |
|
35 |
+ // This will be pulled out of the auth config file after https://github.com/GoogleCloudPlatform/kubernetes/pull/2437 |
|
36 |
+ osCfg := clientCfg.OpenShiftConfig() |
|
37 |
+ osCfg.BearerToken = token |
|
38 |
+ |
|
39 |
+ osClient, err := osclient.New(osCfg) |
|
40 |
+ if err != nil { |
|
41 |
+ fmt.Printf("Error building osClient: %v\n", err) |
|
42 |
+ return |
|
43 |
+ } |
|
44 |
+ |
|
45 |
+ jsonResponse, _, err := getTokenInfo(token, osClient) |
|
46 |
+ if err != nil { |
|
47 |
+ fmt.Printf("%v\n", err) |
|
48 |
+ fmt.Println("Try visiting " + getRequestTokenUrl(clientCfg) + " for a new token.") |
|
49 |
+ return |
|
50 |
+ } |
|
51 |
+ fmt.Printf("%v\n", string(jsonResponse)) |
|
52 |
+ |
|
53 |
+ whoami, err := osClient.Users().Get("~") |
|
54 |
+ if err != nil { |
|
55 |
+ fmt.Printf("Error making whoami request: %v\n", err) |
|
56 |
+ return |
|
57 |
+ } |
|
58 |
+ whoamiJson, err := json.Marshal(whoami) |
|
59 |
+ if err != nil { |
|
60 |
+ fmt.Printf("Error interpretting whoami response: %v\n", err) |
|
61 |
+ return |
|
62 |
+ } |
|
63 |
+ fmt.Printf("%v\n", string(whoamiJson)) |
|
64 |
+} |
|
65 |
+ |
|
66 |
+func getTokenInfo(token string, osClient *osclient.Client) (string, *osintypes.InfoResponseData, error) { |
|
67 |
+ osResult := osClient.Get().AbsPath("oauth").Path("info").Param("code", token).Do() |
|
68 |
+ if osResult.Error() != nil { |
|
69 |
+ return "", nil, fmt.Errorf("Error making info request: %v", osResult.Error()) |
|
70 |
+ } |
|
71 |
+ body, err := osResult.Raw() |
|
72 |
+ if err != nil { |
|
73 |
+ return "", nil, fmt.Errorf("Error reading info response: %v\n", err) |
|
74 |
+ } |
|
75 |
+ glog.V(1).Infof("Raw JSON: %v\n", string(body)) |
|
76 |
+ |
|
77 |
+ var accessData osintypes.InfoResponseData |
|
78 |
+ err = json.Unmarshal(body, &accessData) |
|
79 |
+ if err != nil { |
|
80 |
+ return "", nil, fmt.Errorf("Error while unmarshalling info response: %v %v", err, string(body)) |
|
81 |
+ } |
|
82 |
+ if accessData.Error == "invalid_request" { |
|
83 |
+ return "", nil, fmt.Errorf("\"%v\" is not a valid token.\n", token) |
|
84 |
+ } |
|
85 |
+ if len(accessData.ErrorDescription) != 0 { |
|
86 |
+ return "", nil, fmt.Errorf("%v\n", accessData.ErrorDescription) |
|
87 |
+ } |
|
88 |
+ |
|
89 |
+ return string(body), &accessData, nil |
|
90 |
+ |
|
91 |
+} |
0 | 92 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,66 @@ |
0 |
+package tokens |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "os" |
|
5 |
+ |
|
6 |
+ "github.com/spf13/cobra" |
|
7 |
+ |
|
8 |
+ osclient "github.com/openshift/origin/pkg/client" |
|
9 |
+ "github.com/openshift/origin/pkg/cmd/util/clientcmd" |
|
10 |
+ "github.com/openshift/origin/pkg/cmd/util/tokencmd" |
|
11 |
+) |
|
12 |
+ |
|
13 |
+func NewCmdWhoAmI(clientCfg *clientcmd.Config) *cobra.Command { |
|
14 |
+ cmd := &cobra.Command{ |
|
15 |
+ Use: "whoami", |
|
16 |
+ Short: "checks the identity associated with an access token", |
|
17 |
+ Long: `checks the identity associated with an access token`, |
|
18 |
+ Run: func(cmd *cobra.Command, args []string) { |
|
19 |
+ token := "" |
|
20 |
+ if cmd.Flags().Lookup("token") != nil { |
|
21 |
+ token = getFlagString(cmd, "token") |
|
22 |
+ } |
|
23 |
+ whoami(token, clientCfg) |
|
24 |
+ |
|
25 |
+ }, |
|
26 |
+ } |
|
27 |
+ cmd.Flags().String("token", "", "Token value") |
|
28 |
+ return cmd |
|
29 |
+} |
|
30 |
+ |
|
31 |
+func whoami(token string, clientCfg *clientcmd.Config) { |
|
32 |
+ osCfg := clientCfg.OpenShiftConfig() |
|
33 |
+ // This will be pulled out of the auth config file after https://github.com/GoogleCloudPlatform/kubernetes/pull/2437 |
|
34 |
+ if len(token) > 0 { |
|
35 |
+ osCfg.BearerToken = token |
|
36 |
+ } |
|
37 |
+ |
|
38 |
+ osClient, err := osclient.New(osCfg) |
|
39 |
+ if err != nil { |
|
40 |
+ fmt.Printf("Error building osClient: %v\n", err) |
|
41 |
+ return |
|
42 |
+ } |
|
43 |
+ |
|
44 |
+ me, err := osClient.Users().Get("~") |
|
45 |
+ if err != nil { |
|
46 |
+ // let's pretend that we can determine that we got back a 401. we need an updated kubernetes for this |
|
47 |
+ accessToken, err := tokencmd.RequestToken(clientCfg, os.Stdin) |
|
48 |
+ if err != nil { |
|
49 |
+ fmt.Printf("%v\n", err) |
|
50 |
+ return |
|
51 |
+ } |
|
52 |
+ |
|
53 |
+ osCfg := clientCfg.OpenShiftConfig() |
|
54 |
+ osCfg.BearerToken = accessToken |
|
55 |
+ osClient, _ = osclient.New(osCfg) |
|
56 |
+ |
|
57 |
+ me, err = osClient.Users().Get("~") |
|
58 |
+ if err != nil { |
|
59 |
+ fmt.Printf("Error making request: %v\n", err) |
|
60 |
+ return |
|
61 |
+ } |
|
62 |
+ } |
|
63 |
+ |
|
64 |
+ fmt.Printf("%v\n", me) |
|
65 |
+} |
0 | 66 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,21 @@ |
0 |
+package experimental |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "github.com/spf13/cobra" |
|
4 |
+ |
|
5 |
+ "github.com/openshift/origin/pkg/cmd/experimental/tokens" |
|
6 |
+) |
|
7 |
+ |
|
8 |
+func NewCommandExperimental(name string) *cobra.Command { |
|
9 |
+ cmd := &cobra.Command{ |
|
10 |
+ Use: name, |
|
11 |
+ Run: func(c *cobra.Command, args []string) { |
|
12 |
+ // no usage |
|
13 |
+ // c.Help() |
|
14 |
+ }, |
|
15 |
+ } |
|
16 |
+ |
|
17 |
+ cmd.AddCommand(tokens.NewCommandTokens("tokens")) |
|
18 |
+ |
|
19 |
+ return cmd |
|
20 |
+} |
... | ... |
@@ -11,6 +11,7 @@ import ( |
11 | 11 |
"github.com/openshift/origin/pkg/cmd/flagtypes" |
12 | 12 |
"github.com/openshift/origin/pkg/cmd/infra/builder" |
13 | 13 |
"github.com/openshift/origin/pkg/cmd/infra/deployer" |
14 |
+ "github.com/openshift/origin/pkg/cmd/infra/experimental" |
|
14 | 15 |
"github.com/openshift/origin/pkg/cmd/infra/router" |
15 | 16 |
"github.com/openshift/origin/pkg/cmd/server" |
16 | 17 |
"github.com/openshift/origin/pkg/version" |
... | ... |
@@ -48,6 +49,8 @@ func CommandFor(basename string) *cobra.Command { |
48 | 48 |
return builder.NewCommandDockerBuilder(basename) |
49 | 49 |
case "cli": |
50 | 50 |
return cli.NewCommandCLI(basename) |
51 |
+ case "openshift-experimental": |
|
52 |
+ return experimental.NewCommandExperimental(basename) |
|
51 | 53 |
default: |
52 | 54 |
return NewCommandOpenShift() |
53 | 55 |
} |
... | ... |
@@ -79,6 +82,7 @@ func NewCommandOpenShift() *cobra.Command { |
79 | 79 |
deployer.NewCommandDeployer("deploy"), |
80 | 80 |
builder.NewCommandSTIBuilder("sti-build"), |
81 | 81 |
builder.NewCommandDockerBuilder("docker-build"), |
82 |
+ experimental.NewCommandExperimental("experimental"), |
|
82 | 83 |
) |
83 | 84 |
root.AddCommand(infra) |
84 | 85 |
|
... | ... |
@@ -18,6 +18,7 @@ import ( |
18 | 18 |
"github.com/openshift/origin/pkg/auth/authenticator/basicauthpassword" |
19 | 19 |
"github.com/openshift/origin/pkg/auth/authenticator/bearertoken" |
20 | 20 |
authfile "github.com/openshift/origin/pkg/auth/authenticator/file" |
21 |
+ authhandlers "github.com/openshift/origin/pkg/auth/authenticator/handlers" |
|
21 | 22 |
"github.com/openshift/origin/pkg/auth/authenticator/requesthandlers" |
22 | 23 |
"github.com/openshift/origin/pkg/auth/authenticator/requestheader" |
23 | 24 |
"github.com/openshift/origin/pkg/auth/oauth/external" |
... | ... |
@@ -36,6 +37,7 @@ import ( |
36 | 36 |
cmdutil "github.com/openshift/origin/pkg/cmd/util" |
37 | 37 |
oauthapi "github.com/openshift/origin/pkg/oauth/api" |
38 | 38 |
clientregistry "github.com/openshift/origin/pkg/oauth/registry/client" |
39 |
+ oauthclient "github.com/openshift/origin/pkg/oauth/registry/client" |
|
39 | 40 |
"github.com/openshift/origin/pkg/oauth/registry/clientauthorization" |
40 | 41 |
oauthetcd "github.com/openshift/origin/pkg/oauth/registry/etcd" |
41 | 42 |
"github.com/openshift/origin/pkg/oauth/server/osinserver" |
... | ... |
@@ -59,6 +61,13 @@ var ( |
59 | 59 |
}, |
60 | 60 |
Secret: uuid.NewUUID().String(), // random secret so no one knows what it is ahead of time. This still allows us to loop back for /requestToken |
61 | 61 |
} |
62 |
+ OSCliClientBase = oauthapi.Client{ |
|
63 |
+ ObjectMeta: kapi.ObjectMeta{ |
|
64 |
+ Name: "openshift-challenging-client", |
|
65 |
+ }, |
|
66 |
+ Secret: uuid.NewUUID().String(), // random secret so no one knows what it is ahead of time. This still allows us to loop back for /requestToken |
|
67 |
+ RespondWithChallenges: true, |
|
68 |
+ } |
|
62 | 69 |
) |
63 | 70 |
|
64 | 71 |
type AuthConfig struct { |
... | ... |
@@ -104,11 +113,11 @@ func (c *AuthConfig) InstallAPI(mux cmdutil.Mux) []string { |
104 | 104 |
) |
105 | 105 |
server.Install(mux, OpenShiftOAuthAPIPrefix) |
106 | 106 |
|
107 |
- c.createOrUpdateDefaultOAuthClients() |
|
107 |
+ CreateOrUpdateDefaultOAuthClients(c.MasterAddr, oauthEtcd) |
|
108 | 108 |
osOAuthClientConfig := c.NewOpenShiftOAuthClientConfig(&OSBrowserClientBase) |
109 | 109 |
osOAuthClientConfig.RedirectUrl = c.MasterAddr + OpenShiftOAuthAPIPrefix + tokenrequest.DisplayTokenEndpoint |
110 | 110 |
osOAuthClient, _ := osincli.NewClient(osOAuthClientConfig) |
111 |
- tokenRequestEndpoints := tokenrequest.NewEndpoints(authHandler, osOAuthClient) |
|
111 |
+ tokenRequestEndpoints := tokenrequest.NewEndpoints(osOAuthClient) |
|
112 | 112 |
tokenRequestEndpoints.Install(mux, OpenShiftOAuthAPIPrefix) |
113 | 113 |
|
114 | 114 |
// glog.Infof("oauth server configured as: %#v", server) |
... | ... |
@@ -137,25 +146,32 @@ func (c *AuthConfig) NewOpenShiftOAuthClientConfig(client *oauthapi.Client) *osi |
137 | 137 |
return config |
138 | 138 |
} |
139 | 139 |
|
140 |
-func (c *AuthConfig) createOrUpdateDefaultOAuthClients() { |
|
140 |
+func CreateOrUpdateDefaultOAuthClients(masterAddr string, clientRegistry oauthclient.Registry) { |
|
141 | 141 |
clientsToEnsure := []*oauthapi.Client{ |
142 | 142 |
{ |
143 | 143 |
ObjectMeta: kapi.ObjectMeta{ |
144 | 144 |
Name: OSBrowserClientBase.Name, |
145 | 145 |
}, |
146 |
- Secret: OSBrowserClientBase.Secret, |
|
147 |
- RedirectURIs: []string{c.MasterAddr + OpenShiftOAuthAPIPrefix + tokenrequest.DisplayTokenEndpoint}, |
|
146 |
+ Secret: OSBrowserClientBase.Secret, |
|
147 |
+ RespondWithChallenges: OSBrowserClientBase.RespondWithChallenges, |
|
148 |
+ RedirectURIs: []string{masterAddr + OpenShiftOAuthAPIPrefix + tokenrequest.DisplayTokenEndpoint}, |
|
149 |
+ }, |
|
150 |
+ { |
|
151 |
+ ObjectMeta: kapi.ObjectMeta{ |
|
152 |
+ Name: OSCliClientBase.Name, |
|
153 |
+ }, |
|
154 |
+ Secret: OSCliClientBase.Secret, |
|
155 |
+ RespondWithChallenges: OSCliClientBase.RespondWithChallenges, |
|
156 |
+ RedirectURIs: []string{masterAddr + OpenShiftOAuthAPIPrefix + tokenrequest.DisplayTokenEndpoint}, |
|
148 | 157 |
}, |
149 | 158 |
} |
150 | 159 |
|
151 |
- oauthEtcd := oauthetcd.New(c.EtcdHelper) |
|
152 |
- |
|
153 | 160 |
for _, currClient := range clientsToEnsure { |
154 |
- if existing, err := oauthEtcd.GetClient(currClient.Name); err == nil || strings.Contains(err.Error(), " not found") { |
|
161 |
+ if existing, err := clientRegistry.GetClient(currClient.Name); err == nil || strings.Contains(err.Error(), " not found") { |
|
155 | 162 |
if existing != nil { |
156 |
- oauthEtcd.DeleteClient(currClient.Name) |
|
163 |
+ clientRegistry.DeleteClient(currClient.Name) |
|
157 | 164 |
} |
158 |
- if err = oauthEtcd.CreateClient(currClient); err != nil { |
|
165 |
+ if err = clientRegistry.CreateClient(currClient); err != nil { |
|
159 | 166 |
glog.Errorf("Error creating client: %v due to %v\n", currClient, err) |
160 | 167 |
} |
161 | 168 |
} else { |
... | ... |
@@ -201,7 +217,7 @@ func (c *AuthConfig) getGrantHandler(mux cmdutil.Mux, auth authenticator.Request |
201 | 201 |
return grantHandler |
202 | 202 |
} |
203 | 203 |
|
204 |
-func (c *AuthConfig) getAuthenticationHandler(mux cmdutil.Mux, sessionStore session.Store, error handlers.AuthenticationErrorHandler) handlers.AuthenticationHandler { |
|
204 |
+func (c *AuthConfig) getAuthenticationHandler(mux cmdutil.Mux, sessionStore session.Store, errorHandler handlers.AuthenticationErrorHandler) handlers.AuthenticationHandler { |
|
205 | 205 |
successHandler := c.getAuthenticationSuccessHandler(sessionStore) |
206 | 206 |
|
207 | 207 |
// TODO presumeably we'll want either a list of what we've got or a way to describe a registry of these |
... | ... |
@@ -222,16 +238,20 @@ func (c *AuthConfig) getAuthenticationHandler(mux cmdutil.Mux, sessionStore sess |
222 | 222 |
} |
223 | 223 |
|
224 | 224 |
state := external.DefaultState() |
225 |
- oauthHandler, err := external.NewHandler(oauthProvider, state, c.MasterAddr+callbackPath, successHandler, error, identityMapper) |
|
225 |
+ oauthHandler, err := external.NewExternalOAuthRedirector(oauthProvider, state, c.MasterAddr+callbackPath, successHandler, errorHandler, identityMapper) |
|
226 | 226 |
if err != nil { |
227 | 227 |
glog.Fatalf("unexpected error: %v", err) |
228 | 228 |
} |
229 | 229 |
|
230 | 230 |
mux.Handle(callbackPath, oauthHandler) |
231 |
- authHandler = oauthHandler |
|
231 |
+ authHandler = handlers.NewUnionAuthenticationHandler(nil, map[string]handlers.AuthenticationRedirector{authHandlerType: oauthHandler}, errorHandler) |
|
232 | 232 |
case "login": |
233 | 233 |
passwordAuth := c.getPasswordAuthenticator() |
234 |
- authHandler = &redirectAuthHandler{RedirectURL: OpenShiftLoginPrefix, ThenParam: "then"} |
|
234 |
+ authHandler = handlers.NewUnionAuthenticationHandler( |
|
235 |
+ map[string]handlers.AuthenticationChallenger{"login": authhandlers.NewBasicAuthChallenger("openshift")}, |
|
236 |
+ map[string]handlers.AuthenticationRedirector{"login": &redirector{RedirectURL: OpenShiftLoginPrefix, ThenParam: "then"}}, |
|
237 |
+ errorHandler, |
|
238 |
+ ) |
|
235 | 239 |
login := login.NewLogin(getCSRF(), &callbackPasswordAuthenticator{passwordAuth, successHandler}, login.DefaultLoginFormRenderer) |
236 | 240 |
login.Install(mux, OpenShiftLoginPrefix) |
237 | 241 |
case "empty": |
... | ... |
@@ -347,16 +367,16 @@ func GetTokenAuthenticator(etcdHelper tools.EtcdHelper) (authenticator.Token, er |
347 | 347 |
} |
348 | 348 |
|
349 | 349 |
// Captures the original request url as a "then" param in a redirect to a login flow |
350 |
-type redirectAuthHandler struct { |
|
350 |
+type redirector struct { |
|
351 | 351 |
RedirectURL string |
352 | 352 |
ThenParam string |
353 | 353 |
} |
354 | 354 |
|
355 |
-// AuthenticationNeeded redirects HTTP request to authorization URL |
|
356 |
-func (auth *redirectAuthHandler) AuthenticationNeeded(w http.ResponseWriter, req *http.Request) (bool, error) { |
|
355 |
+// AuthenticationRedirectNeeded redirects HTTP request to authorization URL |
|
356 |
+func (auth *redirector) AuthenticationRedirect(w http.ResponseWriter, req *http.Request) error { |
|
357 | 357 |
redirectURL, err := url.Parse(auth.RedirectURL) |
358 | 358 |
if err != nil { |
359 |
- return false, err |
|
359 |
+ return err |
|
360 | 360 |
} |
361 | 361 |
if len(auth.ThenParam) != 0 { |
362 | 362 |
redirectURL.RawQuery = url.Values{ |
... | ... |
@@ -364,7 +384,7 @@ func (auth *redirectAuthHandler) AuthenticationNeeded(w http.ResponseWriter, req |
364 | 364 |
}.Encode() |
365 | 365 |
} |
366 | 366 |
http.Redirect(w, req, redirectURL.String(), http.StatusFound) |
367 |
- return true, nil |
|
367 |
+ return nil |
|
368 | 368 |
} |
369 | 369 |
|
370 | 370 |
// |
... | ... |
@@ -181,12 +181,12 @@ func (c *MasterConfig) RunAPI(installers ...APIInstaller) { |
181 | 181 |
} |
182 | 182 |
handlerContainer := kmaster.NewHandlerContainer(osMux) |
183 | 183 |
apiserver.NewAPIGroupVersion(storage, v1beta1.Codec, OpenShiftAPIPrefixV1Beta1, latest.SelfLinker).InstallREST(handlerContainer, "/osapi", "v1beta1") |
184 |
- //apiserver.InstallSupport(osMux) |
|
185 | 184 |
|
186 | 185 |
handler := http.Handler(osMux) |
187 | 186 |
if c.RequireAuthentication { |
188 |
- handler = c.wrapHandlerWithAuthentication(handler) |
|
187 |
+ handler = c.wireAuthenticationHandling(osMux, handler) |
|
189 | 188 |
} |
189 |
+ |
|
190 | 190 |
if len(c.CORSAllowedOrigins) > 0 { |
191 | 191 |
handler = apiserver.CORS(handler, c.CORSAllowedOrigins, nil, nil, "true") |
192 | 192 |
} |
... | ... |
@@ -210,9 +210,37 @@ func (c *MasterConfig) RunAPI(installers ...APIInstaller) { |
210 | 210 |
}, 0) |
211 | 211 |
} |
212 | 212 |
|
213 |
-func (c *MasterConfig) wrapHandlerWithAuthentication(handler http.Handler) http.Handler { |
|
213 |
+// wireAuthenticationHandling creates and binds all the objects that we only care about if authentication is turned on. It's pulled out |
|
214 |
+// just to make the RunAPI method easier to read. These resources include the requestsToUsers map that allows callers to know which user |
|
215 |
+// is requesting an operation, the handler wrapper that protects the passed handler behind a handler that requires valid authentication |
|
216 |
+// information on the request, and an endpoint that only functions properly with an authenticated user. |
|
217 |
+func (c *MasterConfig) wireAuthenticationHandling(osMux *http.ServeMux, handler http.Handler) http.Handler { |
|
218 |
+ // this tracks requests back to users for authorization. The same instance must be shared between writers and readers |
|
219 |
+ requestsToUsers := authcontext.NewRequestContextMap() |
|
220 |
+ |
|
221 |
+ // wrapHandlerWithAuthentication binds a handler that will correlate the users and requests |
|
222 |
+ handler = c.wrapHandlerWithAuthentication(handler, requestsToUsers) |
|
223 |
+ |
|
224 |
+ // this requires the requests and users to be present |
|
225 |
+ thisUserEndpoint := OpenShiftAPIPrefixV1Beta1 + "/users/~" |
|
226 |
+ userContextMap := userregistry.ContextFunc(func(req *http.Request) (userregistry.Info, bool) { |
|
227 |
+ obj, found := requestsToUsers.Get(req) |
|
228 |
+ if user, ok := obj.(userregistry.Info); found && ok { |
|
229 |
+ return user, true |
|
230 |
+ } |
|
231 |
+ return nil, false |
|
232 |
+ }) |
|
233 |
+ userregistry.InstallThisUser(osMux, thisUserEndpoint, userContextMap, handler) |
|
234 |
+ |
|
235 |
+ return handler |
|
236 |
+} |
|
237 |
+ |
|
238 |
+// wrapHandlerWithAuthentication takes a handler and protects it behind a handler that tests to make sure that a user is authenticated. |
|
239 |
+// if the request does have value auth information, then the request is allowed through the passed handler. If the request does not have |
|
240 |
+// valid auth information, then the request is passed to a failure handler. Until we get authentication for system componenets, the |
|
241 |
+// failure handler logs and passes through. |
|
242 |
+func (c *MasterConfig) wrapHandlerWithAuthentication(handler http.Handler, requestsToUsers *authcontext.RequestContextMap) http.Handler { |
|
214 | 243 |
// wrap with authenticated token check |
215 |
- requestsToUsers := authcontext.NewRequestContextMap() // this tracks requests back to users for authorization |
|
216 | 244 |
tokenAuthenticator, err := GetTokenAuthenticator(c.EtcdHelper) |
217 | 245 |
if err != nil { |
218 | 246 |
glog.Fatalf("Error creating TokenAuthenticator: %v. The oauth server cannot start!", err) |
... | ... |
@@ -56,7 +56,7 @@ func (cfg *Config) bindEnv() { |
56 | 56 |
} |
57 | 57 |
} |
58 | 58 |
|
59 |
-func (cfg *Config) Clients() (kclient.Interface, osclient.Interface, error) { |
|
59 |
+func (cfg *Config) KubeConfig() *kclient.Config { |
|
60 | 60 |
cfg.bindEnv() |
61 | 61 |
|
62 | 62 |
kaddr := cfg.KubernetesAddr |
... | ... |
@@ -66,14 +66,28 @@ func (cfg *Config) Clients() (kclient.Interface, osclient.Interface, error) { |
66 | 66 |
|
67 | 67 |
kConfig := cfg.CommonConfig |
68 | 68 |
kConfig.Host = kaddr.URL.String() |
69 |
- kubeClient, err := kclient.New(&kConfig) |
|
69 |
+ |
|
70 |
+ return &kConfig |
|
71 |
+} |
|
72 |
+ |
|
73 |
+func (cfg *Config) OpenShiftConfig() *kclient.Config { |
|
74 |
+ cfg.bindEnv() |
|
75 |
+ |
|
76 |
+ osConfig := cfg.CommonConfig |
|
77 |
+ osConfig.Host = cfg.MasterAddr.String() |
|
78 |
+ |
|
79 |
+ return &osConfig |
|
80 |
+} |
|
81 |
+ |
|
82 |
+func (cfg *Config) Clients() (kclient.Interface, osclient.Interface, error) { |
|
83 |
+ cfg.bindEnv() |
|
84 |
+ |
|
85 |
+ kubeClient, err := kclient.New(cfg.KubeConfig()) |
|
70 | 86 |
if err != nil { |
71 | 87 |
return nil, nil, fmt.Errorf("Unable to configure Kubernetes client: %v", err) |
72 | 88 |
} |
73 | 89 |
|
74 |
- osConfig := cfg.CommonConfig |
|
75 |
- osConfig.Host = cfg.MasterAddr.String() |
|
76 |
- osClient, err := osclient.New(&osConfig) |
|
90 |
+ osClient, err := osclient.New(cfg.OpenShiftConfig()) |
|
77 | 91 |
if err != nil { |
78 | 92 |
return nil, nil, fmt.Errorf("Unable to configure OpenShift client: %v", err) |
79 | 93 |
} |
80 | 94 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,126 @@ |
0 |
+package tokencmd |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "errors" |
|
4 |
+ "fmt" |
|
5 |
+ "io" |
|
6 |
+ "net/http" |
|
7 |
+ "regexp" |
|
8 |
+ "strings" |
|
9 |
+ |
|
10 |
+ kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" |
|
11 |
+ |
|
12 |
+ "github.com/openshift/origin/pkg/auth/server/tokenrequest" |
|
13 |
+ "github.com/openshift/origin/pkg/cmd/util/clientcmd" |
|
14 |
+) |
|
15 |
+ |
|
16 |
+const ( |
|
17 |
+ accessTokenRedirectPattern = `#access_token=([\w]+)&` |
|
18 |
+) |
|
19 |
+ |
|
20 |
+var ( |
|
21 |
+ accessTokenRedirectRegex = regexp.MustCompile(accessTokenRedirectPattern) |
|
22 |
+) |
|
23 |
+ |
|
24 |
+type tokenGetterInfo struct { |
|
25 |
+ accessToken string |
|
26 |
+} |
|
27 |
+ |
|
28 |
+// RequestToken uses the cmd arguments to locate an openshift oauth server and attempts to authenticate |
|
29 |
+// it returns the access token if it gets one. An error if it does not |
|
30 |
+func RequestToken(clientCfg *clientcmd.Config, reader io.Reader) (string, error) { |
|
31 |
+ tokenGetter := &tokenGetterInfo{} |
|
32 |
+ |
|
33 |
+ kubeCfg := clientCfg.KubeConfig() |
|
34 |
+ kubeClient, err := kclient.New(kubeCfg) |
|
35 |
+ if err != nil { |
|
36 |
+ return "", err |
|
37 |
+ } |
|
38 |
+ |
|
39 |
+ // get the transport, so that we can use it to build our own client that wraps it |
|
40 |
+ // our client understands certain challenges and can respond to them |
|
41 |
+ clientTransport, err := kclient.TransportFor(kubeCfg) |
|
42 |
+ if err != nil { |
|
43 |
+ return "", err |
|
44 |
+ } |
|
45 |
+ httpClient := &http.Client{ |
|
46 |
+ Transport: clientTransport, |
|
47 |
+ CheckRedirect: tokenGetter.checkRedirect, |
|
48 |
+ } |
|
49 |
+ kubeClient.Client = &challengingClient{httpClient, reader} |
|
50 |
+ |
|
51 |
+ _ = kubeClient.Get().AbsPath("oauth").Path("authorize").Param("response_type", "token").Param("client_id", "openshift-challenging-client").Do() |
|
52 |
+ |
|
53 |
+ if len(tokenGetter.accessToken) == 0 { |
|
54 |
+ requestTokenUrl := kubeCfg.Host + "/oauth" /* clean up after auth.go dies */ + tokenrequest.RequestTokenEndpoint |
|
55 |
+ return "", errors.New("Unable to get token. Try visiting " + requestTokenUrl + " for a new token.") |
|
56 |
+ } |
|
57 |
+ |
|
58 |
+ return tokenGetter.accessToken, nil |
|
59 |
+} |
|
60 |
+ |
|
61 |
+// checkRedirect watches the redirects to see if any contain the access_token anchor. It then stores the value of the access token for later retrieval |
|
62 |
+func (tokenGetter *tokenGetterInfo) checkRedirect(req *http.Request, via []*http.Request) error { |
|
63 |
+ // if we're redirected with an access token in the anchor, use it to set our transport to a proper bearer auth |
|
64 |
+ if matches := accessTokenRedirectRegex.FindAllStringSubmatch(req.URL.String(), 1); matches != nil { |
|
65 |
+ tokenGetter.accessToken = matches[0][1] |
|
66 |
+ } |
|
67 |
+ |
|
68 |
+ if len(via) >= 10 { |
|
69 |
+ return errors.New("stopped after 10 redirects") |
|
70 |
+ } |
|
71 |
+ |
|
72 |
+ return nil |
|
73 |
+} |
|
74 |
+ |
|
75 |
+// challengingClient conforms the kclient.HTTPClient interface. It introspects responses for auth challenges and |
|
76 |
+// tries to response to those challenges in order to get a token back. |
|
77 |
+type challengingClient struct { |
|
78 |
+ delegate *http.Client |
|
79 |
+ reader io.Reader |
|
80 |
+} |
|
81 |
+ |
|
82 |
+const ( |
|
83 |
+ basicAuthPattern = `[\s]*Basic[\s]*realm="([\w]+)"` |
|
84 |
+) |
|
85 |
+ |
|
86 |
+var ( |
|
87 |
+ basicAuthRegex = regexp.MustCompile(basicAuthPattern) |
|
88 |
+) |
|
89 |
+ |
|
90 |
+// Do watches for unauthorized challenges. If we know to respond, we respond to the challenge |
|
91 |
+func (client *challengingClient) Do(req *http.Request) (*http.Response, error) { |
|
92 |
+ resp, err := client.delegate.Do(req) |
|
93 |
+ if resp.StatusCode == http.StatusUnauthorized { |
|
94 |
+ if wantsBasicAuth, realm := isBasicAuthChallenge(resp); wantsBasicAuth { |
|
95 |
+ fmt.Printf("Authenticate for \"%v\"\n", realm) |
|
96 |
+ username := promptForString("username", client.reader) |
|
97 |
+ password := promptForString("password", client.reader) |
|
98 |
+ |
|
99 |
+ client.delegate.Transport = kclient.NewBasicAuthRoundTripper(username, password, client.delegate.Transport) |
|
100 |
+ return client.Do(resp.Request) |
|
101 |
+ } |
|
102 |
+ } |
|
103 |
+ return resp, err |
|
104 |
+} |
|
105 |
+ |
|
106 |
+func isBasicAuthChallenge(resp *http.Response) (bool, string) { |
|
107 |
+ for currHeader, headerValue := range resp.Header { |
|
108 |
+ if strings.EqualFold(currHeader, "WWW-Authenticate") { |
|
109 |
+ for _, currAuthorizeHeader := range headerValue { |
|
110 |
+ if matches := basicAuthRegex.FindAllStringSubmatch(currAuthorizeHeader, 1); matches != nil { |
|
111 |
+ return true, matches[0][1] |
|
112 |
+ } |
|
113 |
+ } |
|
114 |
+ } |
|
115 |
+ } |
|
116 |
+ |
|
117 |
+ return false, "" |
|
118 |
+} |
|
119 |
+ |
|
120 |
+func promptForString(field string, r io.Reader) string { |
|
121 |
+ fmt.Printf("Please enter %s: ", field) |
|
122 |
+ var result string |
|
123 |
+ fmt.Fscan(r, &result) |
|
124 |
+ return result |
|
125 |
+} |
... | ... |
@@ -67,6 +67,9 @@ type Client struct { |
67 | 67 |
// Secret is the unique secret associated with a client |
68 | 68 |
Secret string `json:"secret,omitempty" yaml:"secret,omitempty"` |
69 | 69 |
|
70 |
+ // RespondWithChallenges indicates whether the client wants authentication needed responses made in the form of challenges instead of redirects |
|
71 |
+ RespondWithChallenges bool `json:"respondWithChallenges,omitempty" yaml:"respondWithChallenges,omitempty"` |
|
72 |
+ |
|
70 | 73 |
// RedirectURIs is the valid redirection URIs associated with a client |
71 | 74 |
RedirectURIs []string `json:"redirectURIs,omitempty" yaml:"redirectURIs,omitempty"` |
72 | 75 |
} |
... | ... |
@@ -67,6 +67,9 @@ type Client struct { |
67 | 67 |
// Secret is the unique secret associated with a client |
68 | 68 |
Secret string `json:"secret,omitempty" yaml:"secret,omitempty"` |
69 | 69 |
|
70 |
+ // RespondWithChallenges indicates whether the client wants authentication needed responses made in the form of challenges instead of redirects |
|
71 |
+ RespondWithChallenges bool `json:"respondWithChallenges,omitempty" yaml:"respondWithChallenges,omitempty"` |
|
72 |
+ |
|
70 | 73 |
// RedirectURIs is the valid redirection URIs associated with a client |
71 | 74 |
RedirectURIs []string `json:"redirectURIs,omitempty" yaml:"redirectURIs,omitempty"` |
72 | 75 |
} |
73 | 76 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,17 @@ |
0 |
+// osintypes holds types that are used to interpret responses from the RangelReale osin server. |
|
1 |
+// neither osin, nor osincli contain types with the annotations required to have the default json |
|
2 |
+// marshaller encode and decode them. Even more unusual: osin does not describe a single type that |
|
3 |
+// represents the overall return from osin.FinishInfoRequest. Because of that, a type needs to be |
|
4 |
+// described in order to make use of the return value, so even if you preferred writing a parser |
|
5 |
+// you'll end up needing a type anyway. |
|
6 |
+package osintypes |
|
7 |
+ |
|
8 |
+// InfoResponseData is a type that matches the information returned from osin.FinishInfoRequest (/oauth/info). |
|
9 |
+type InfoResponseData struct { |
|
10 |
+ Error string `json:"error" yaml:"error"` |
|
11 |
+ ErrorDescription string `json:"error_description" yaml:"error_description"` |
|
12 |
+ TokenType string `json:"token_type" yaml:"token_type"` |
|
13 |
+ AccessToken string `json:"access_token" yaml:"access_token"` |
|
14 |
+ RefreshToken string `json:"refresh_token" yaml:"refresh_token"` |
|
15 |
+ Expiration int32 `json:"expires_in" yaml:"expires_in"` |
|
16 |
+} |
... | ... |
@@ -60,7 +60,7 @@ func (w *clientWrapper) GetRedirectUri() string { |
60 | 60 |
} |
61 | 61 |
|
62 | 62 |
func (w *clientWrapper) GetUserData() interface{} { |
63 |
- return nil |
|
63 |
+ return w.client |
|
64 | 64 |
} |
65 | 65 |
|
66 | 66 |
// Clone the storage if needed. For example, using mgo, you can clone the session with session.Clone |
... | ... |
@@ -10,6 +10,12 @@ type Info interface { |
10 | 10 |
GetUID() string |
11 | 11 |
} |
12 | 12 |
|
13 |
+// mux is an object that can register http handlers. |
|
14 |
+type mux interface { |
|
15 |
+ Handle(pattern string, handler http.Handler) |
|
16 |
+ HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) |
|
17 |
+} |
|
18 |
+ |
|
13 | 19 |
type Context interface { |
14 | 20 |
Get(*http.Request) (Info, bool) |
15 | 21 |
} |
... | ... |
@@ -38,3 +44,20 @@ func NewCurrentContextFilter(requestPath string, context Context, handler http.H |
38 | 38 |
handler.ServeHTTP(w, req) |
39 | 39 |
}) |
40 | 40 |
} |
41 |
+ |
|
42 |
+// InstallThisUser registers the APIServer log support function into a mux. |
|
43 |
+func InstallThisUser(mux mux, endpoint string, requestsToUsers Context, apiHandler http.Handler) { |
|
44 |
+ mux.HandleFunc(endpoint, |
|
45 |
+ func(w http.ResponseWriter, req *http.Request) { |
|
46 |
+ user, found := requestsToUsers.Get(req) |
|
47 |
+ if !found { |
|
48 |
+ http.Error(w, "Need to be authorized to access this method", http.StatusUnauthorized) |
|
49 |
+ return |
|
50 |
+ } |
|
51 |
+ |
|
52 |
+ base := path.Dir(req.URL.Path) |
|
53 |
+ req.URL.Path = path.Join(base, user.GetName()) |
|
54 |
+ apiHandler.ServeHTTP(w, req) |
|
55 |
+ }, |
|
56 |
+ ) |
|
57 |
+} |
41 | 58 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,111 @@ |
0 |
+// +build integration,!no-etcd |
|
1 |
+ |
|
2 |
+package integration |
|
3 |
+ |
|
4 |
+import ( |
|
5 |
+ "bytes" |
|
6 |
+ "net/http" |
|
7 |
+ "net/http/httptest" |
|
8 |
+ "strings" |
|
9 |
+ "testing" |
|
10 |
+ |
|
11 |
+ "github.com/golang/glog" |
|
12 |
+ "github.com/spf13/pflag" |
|
13 |
+ |
|
14 |
+ klatest "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" |
|
15 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/master" |
|
16 |
+ |
|
17 |
+ // for osinserver setup. |
|
18 |
+ "github.com/openshift/origin/pkg/auth/authenticator/anyauthpassword" |
|
19 |
+ authhandlers "github.com/openshift/origin/pkg/auth/authenticator/handlers" |
|
20 |
+ "github.com/openshift/origin/pkg/auth/authenticator/requesthandlers" |
|
21 |
+ oauthhandlers "github.com/openshift/origin/pkg/auth/oauth/handlers" |
|
22 |
+ oauthregistry "github.com/openshift/origin/pkg/auth/oauth/registry" |
|
23 |
+ "github.com/openshift/origin/pkg/auth/userregistry/identitymapper" |
|
24 |
+ "github.com/openshift/origin/pkg/cmd/server/origin" |
|
25 |
+ oauthetcd "github.com/openshift/origin/pkg/oauth/registry/etcd" |
|
26 |
+ "github.com/openshift/origin/pkg/oauth/server/osinserver" |
|
27 |
+ "github.com/openshift/origin/pkg/oauth/server/osinserver/registrystorage" |
|
28 |
+ "github.com/openshift/origin/pkg/user" |
|
29 |
+ useretcd "github.com/openshift/origin/pkg/user/registry/etcd" |
|
30 |
+ |
|
31 |
+ "github.com/openshift/origin/pkg/cmd/util/clientcmd" |
|
32 |
+ "github.com/openshift/origin/pkg/cmd/util/tokencmd" |
|
33 |
+) |
|
34 |
+ |
|
35 |
+func init() { |
|
36 |
+ requireEtcd() |
|
37 |
+} |
|
38 |
+ |
|
39 |
+func TestGetToken(t *testing.T) { |
|
40 |
+ deleteAllEtcdKeys() |
|
41 |
+ |
|
42 |
+ // setup |
|
43 |
+ etcdClient := newEtcdClient() |
|
44 |
+ etcdHelper, _ := master.NewEtcdHelper(etcdClient, klatest.Version) |
|
45 |
+ oauthEtcd := oauthetcd.New(etcdHelper) |
|
46 |
+ userRegistry := useretcd.New(etcdHelper, user.NewDefaultUserInitStrategy()) |
|
47 |
+ identityMapper := identitymapper.NewAlwaysCreateUserIdentityToUserMapper("front-proxy-test" /*for now*/, userRegistry) |
|
48 |
+ |
|
49 |
+ authRequestHandler := requesthandlers.NewBasicAuthAuthentication(anyauthpassword.New(identityMapper)) |
|
50 |
+ authHandler := oauthhandlers.NewUnionAuthenticationHandler( |
|
51 |
+ map[string]oauthhandlers.AuthenticationChallenger{"login": authhandlers.NewBasicAuthChallenger("openshift")}, nil, nil) |
|
52 |
+ |
|
53 |
+ storage := registrystorage.New(oauthEtcd, oauthEtcd, oauthEtcd, oauthregistry.NewUserConversion()) |
|
54 |
+ config := osinserver.NewDefaultServerConfig() |
|
55 |
+ |
|
56 |
+ grantChecker := oauthregistry.NewClientAuthorizationGrantChecker(oauthEtcd) |
|
57 |
+ grantHandler := oauthhandlers.NewAutoGrant(oauthEtcd) |
|
58 |
+ |
|
59 |
+ server := osinserver.New( |
|
60 |
+ config, |
|
61 |
+ storage, |
|
62 |
+ osinserver.AuthorizeHandlers{ |
|
63 |
+ oauthhandlers.NewAuthorizeAuthenticator( |
|
64 |
+ authRequestHandler, |
|
65 |
+ authHandler, |
|
66 |
+ oauthhandlers.EmptyError{}, |
|
67 |
+ ), |
|
68 |
+ oauthhandlers.NewGrantCheck( |
|
69 |
+ grantChecker, |
|
70 |
+ grantHandler, |
|
71 |
+ oauthhandlers.EmptyError{}, |
|
72 |
+ ), |
|
73 |
+ }, |
|
74 |
+ osinserver.AccessHandlers{ |
|
75 |
+ oauthhandlers.NewDenyAccessAuthenticator(), |
|
76 |
+ }, |
|
77 |
+ osinserver.NewDefaultErrorHandler(), |
|
78 |
+ ) |
|
79 |
+ mux := http.NewServeMux() |
|
80 |
+ server.Install(mux, origin.OpenShiftOAuthAPIPrefix) |
|
81 |
+ oauthServer := httptest.NewServer(http.Handler(mux)) |
|
82 |
+ glog.Infof("oauth server is on %v\n", oauthServer.URL) |
|
83 |
+ |
|
84 |
+ // create the default oauth clients with redirects to our server |
|
85 |
+ origin.CreateOrUpdateDefaultOAuthClients(oauthServer.URL, oauthEtcd) |
|
86 |
+ |
|
87 |
+ flags := pflag.NewFlagSet("test-flags", pflag.ContinueOnError) |
|
88 |
+ clientCfg := clientcmd.NewConfig() |
|
89 |
+ clientCfg.Bind(flags) |
|
90 |
+ flags.Parse(strings.Split("--master="+oauthServer.URL, " ")) |
|
91 |
+ |
|
92 |
+ reader := bytes.NewBufferString("user\npass") |
|
93 |
+ accessToken, err := tokencmd.RequestToken(clientCfg, reader) |
|
94 |
+ |
|
95 |
+ if err != nil { |
|
96 |
+ t.Errorf("Unexpected error: %v", err) |
|
97 |
+ } |
|
98 |
+ if len(accessToken) == 0 { |
|
99 |
+ t.Error("Expected accessToken, but did not get one") |
|
100 |
+ } |
|
101 |
+ |
|
102 |
+ // lets see if this access token is any good |
|
103 |
+ token, err := oauthEtcd.GetAccessToken(accessToken) |
|
104 |
+ if err != nil { |
|
105 |
+ t.Errorf("Unexpected error: %v", err) |
|
106 |
+ } |
|
107 |
+ if token.UserName != "front-proxy-test:user" { |
|
108 |
+ t.Errorf("Expected token for \"user\", but got: %#v", token) |
|
109 |
+ } |
|
110 |
+} |