Skip to content

Commit

Permalink
add userinfo endpoint
Browse files Browse the repository at this point in the history
Signed-off-by: hongming <[email protected]>
  • Loading branch information
wansir committed Sep 17, 2021
1 parent 8c5c6a7 commit 97326a8
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 116 deletions.
8 changes: 5 additions & 3 deletions pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ func (s *APIServer) installKubeSphereAPIs() {
userLister := s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister()
urlruntime.Must(oauth.AddToContainer(s.container, imOperator,
auth.NewTokenOperator(s.CacheClient, s.Issuer, s.Config.AuthenticationOptions),
auth.NewPasswordAuthenticator(userLister, s.Config.AuthenticationOptions),
auth.NewOAuthAuthenticator(userLister, s.Config.AuthenticationOptions),
auth.NewPasswordAuthenticator(s.KubernetesClient.KubeSphere(), userLister, s.Config.AuthenticationOptions),
auth.NewOAuthAuthenticator(s.KubernetesClient.KubeSphere(), userLister, s.Config.AuthenticationOptions),
auth.NewLoginRecorder(s.KubernetesClient.KubeSphere(), userLister),
s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.Config.ServiceMeshOptions, s.container, s.KubernetesClient.Kubernetes(), s.CacheClient))
Expand Down Expand Up @@ -337,7 +337,9 @@ func (s *APIServer) buildHandlerChain(stopCh <-chan struct{}) {

// authenticators are unordered
authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(auth.NewPasswordAuthenticator(userLister,
basictoken.New(basic.NewBasicAuthenticator(auth.NewPasswordAuthenticator(
s.KubernetesClient.KubeSphere(),
userLister,
s.Config.AuthenticationOptions),
loginRecorder)),
bearertoken.New(jwt.NewTokenAuthenticator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ endpoint:
userInfoUrl: "https://xxxxx.login.aliyunidaas.com/api/bff/v1.2/oauth2/userinfo"
authURL: "https://xxxx.login.aliyunidaas.com/oauth/authorize"
tokenURL: "https://xxxx.login.aliyunidaas.com/oauth/token"
redirectURL: "https://console.kubesphere.io/oauth/redirect/idaas"
redirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/idaas"
scopes:
- read
`)},
Expand All @@ -65,7 +65,7 @@ scopes:
TokenURL: "https://xxxx.login.aliyunidaas.com/oauth/token",
UserInfoURL: "https://xxxxx.login.aliyunidaas.com/api/bff/v1.2/oauth2/userinfo",
},
RedirectURL: "https://console.kubesphere.io/oauth/redirect/idaas",
RedirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/idaas",
Scopes: []string{"read"},
Config: &oauth2.Config{
ClientID: "xxxx",
Expand All @@ -75,7 +75,7 @@ scopes:
TokenURL: "https://xxxx.login.aliyunidaas.com/oauth/token",
AuthStyle: oauth2.AuthStyleAutoDetect,
},
RedirectURL: "https://console.kubesphere.io/oauth/redirect/idaas",
RedirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/idaas",
Scopes: []string{"read"},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ var _ = Describe("GitHub", func() {
configYAML := `
clientID: de6ff8bed0304e487b6e
clientSecret: 2b70536f79ec8d2939863509d05e2a71c268b9af
redirectURL: "https://console.kubesphere.io/oauth/redirect/github"
redirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/github"
scopes:
- user
`
Expand All @@ -103,7 +103,7 @@ scopes:
TokenURL: tokenURL,
UserInfoURL: userInfoURL,
},
RedirectURL: "https://console.kubesphere.io/oauth/redirect/github",
RedirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
Scopes: []string{"user"},
Config: &oauth2.Config{
ClientID: "de6ff8bed0304e487b6e",
Expand All @@ -112,7 +112,7 @@ scopes:
AuthURL: authURL,
TokenURL: tokenURL,
},
RedirectURL: "https://console.kubesphere.io/oauth/redirect/github",
RedirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
Scopes: []string{"user"},
},
}
Expand All @@ -122,7 +122,7 @@ scopes:
config := oauth.DynamicOptions{
"clientID": "de6ff8bed0304e487b6e",
"clientSecret": "2b70536f79ec8d2939863509d05e2a71c268b9af",
"redirectURL": "https://console.kubesphere.io/oauth/redirect/github",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
"insecureSkipVerify": true,
"endpoint": oauth.DynamicOptions{
"authURL": fmt.Sprintf("%s/login/oauth/authorize", githubServer.URL),
Expand All @@ -136,7 +136,7 @@ scopes:
expected := oauth.DynamicOptions{
"clientID": "de6ff8bed0304e487b6e",
"clientSecret": "2b70536f79ec8d2939863509d05e2a71c268b9af",
"redirectURL": "https://console.kubesphere.io/oauth/redirect/github",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
"insecureSkipVerify": true,
"endpoint": oauth.DynamicOptions{
"authURL": fmt.Sprintf("%s/login/oauth/authorize", githubServer.URL),
Expand All @@ -147,7 +147,7 @@ scopes:
Expect(config).Should(Equal(expected))
})
It("should login successfully", func() {
url, _ := url.Parse("https://console.kubesphere.io/oauth/redirect/test?code=00000")
url, _ := url.Parse("https://ks-console.kubesphere-system.svc/oauth/redirect/test?code=00000")
req := &http.Request{URL: url}
identity, err := provider.IdentityExchangeCallback(req)
Expect(err).Should(BeNil())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ var _ = Describe("OIDC", func() {
"issuer": oidcServer.URL,
"clientID": "kubesphere",
"clientSecret": "c53e80ab92d48ab12f4e7f1f6976d1bdc996e0d7",
"redirectURL": "https://console.kubesphere.io/oauth/redirect/oidc",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/oidc",
"insecureSkipVerify": true,
}
factory := oidcProviderFactory{}
Expand All @@ -181,7 +181,7 @@ var _ = Describe("OIDC", func() {
"issuer": oidcServer.URL,
"clientID": "kubesphere",
"clientSecret": "c53e80ab92d48ab12f4e7f1f6976d1bdc996e0d7",
"redirectURL": "https://console.kubesphere.io/oauth/redirect/oidc",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/oidc",
"insecureSkipVerify": true,
"endpoint": oauth.DynamicOptions{
"authURL": fmt.Sprintf("%s/authorize", oidcServer.URL),
Expand All @@ -194,7 +194,7 @@ var _ = Describe("OIDC", func() {
Expect(config).Should(Equal(expected))
})
It("should login successfully", func() {
url, _ := url.Parse("https://console.kubesphere.io/oauth/redirect/oidc?code=00000")
url, _ := url.Parse("https://ks-console.kubesphere-system.svc/oauth/redirect/oidc?code=00000")
req := &http.Request{URL: url}
identity, err := provider.IdentityExchangeCallback(req)
Expect(err).Should(BeNil())
Expand Down
3 changes: 2 additions & 1 deletion pkg/apiserver/authentication/oauth/error.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 The KubeSphere Authors.
Copyright 2021 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -18,6 +18,7 @@ package oauth

import "fmt"

// The following error type is defined in https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
var (
// ErrorInvalidClient
// Client authentication failed (e.g., unknown client, no
Expand Down
21 changes: 2 additions & 19 deletions pkg/apiserver/authentication/oauth/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ type Token struct {
type Client struct {
// The name of the OAuth client is used as the client_id parameter when making requests to <master>/oauth/authorize
// and <master>/oauth/token.
Name string
Name string `json:"name" yaml:"name,omitempty"`

// Secret is the unique secret associated with a client
Secret string `json:"-" yaml:"secret,omitempty"`
Expand Down Expand Up @@ -225,19 +225,7 @@ type Client struct {

var (
// AllowAllRedirectURI Allow any redirect URI if the redirectURI is defined in request
AllowAllRedirectURI = "*"
DefaultTokenMaxAge = time.Second * 86400
DefaultAccessTokenInactivityTimeout = time.Duration(0)
DefaultClients = []Client{{
Name: "default",
Secret: "kubesphere",
RespondWithChallenges: true,
RedirectURIs: []string{AllowAllRedirectURI},
GrantMethod: GrantHandlerAuto,
ScopeRestrictions: []string{"full"},
AccessTokenMaxAge: &DefaultTokenMaxAge,
AccessTokenInactivityTimeout: &DefaultAccessTokenInactivityTimeout,
}}
AllowAllRedirectURI = "*"
)

func (o *Options) OAuthClient(name string) (Client, error) {
Expand All @@ -246,11 +234,6 @@ func (o *Options) OAuthClient(name string) (Client, error) {
return found, nil
}
}
for _, defaultClient := range DefaultClients {
if defaultClient.Name == name {
return defaultClient, nil
}
}
return Client{}, ErrorClientNotFound
}

Expand Down
56 changes: 9 additions & 47 deletions pkg/apiserver/authentication/oauth/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,77 +19,39 @@ package oauth
import (
"encoding/json"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"gopkg.in/yaml.v3"
)

func TestDefaultAuthOptions(t *testing.T) {
oneDay := time.Second * 86400
zero := time.Duration(0)
expect := Client{
Name: "default",
RespondWithChallenges: true,
Secret: "kubesphere",
RedirectURIs: []string{AllowAllRedirectURI},
GrantMethod: GrantHandlerAuto,
ScopeRestrictions: []string{"full"},
AccessTokenMaxAge: &oneDay,
AccessTokenInactivityTimeout: &zero,
}

options := NewOptions()
client, err := options.OAuthClient("default")
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(expect, client); len(diff) != 0 {
t.Errorf("%T differ (-got, +expected), %s", expect, diff)
}
}

func TestClientResolveRedirectURL(t *testing.T) {
options := NewOptions()
defaultClient, err := options.OAuthClient("default")
if err != nil {
t.Fatal(err)
}

tests := []struct {
Name string
client Client
wantErr bool
expectURL string
}{
{
Name: "default client test",
client: defaultClient,
wantErr: false,
expectURL: "https://localhost:8080/auth/cb",
},
{
Name: "custom client test",
client: Client{
Name: "default",
Name: "custom",
RespondWithChallenges: true,
RedirectURIs: []string{"https://foo.bar.com/oauth/cb"},
RedirectURIs: []string{AllowAllRedirectURI, "https://foo.bar.com/oauth/cb"},
GrantMethod: GrantHandlerAuto,
ScopeRestrictions: []string{"full"},
},
wantErr: true,
expectURL: "https://foo.bar.com/oauth/err",
wantErr: false,
expectURL: "https://foo.bar.com/oauth/cb",
},
{
Name: "custom client test",
client: Client{
Name: "default",
Name: "custom",
RespondWithChallenges: true,
RedirectURIs: []string{AllowAllRedirectURI, "https://foo.bar.com/oauth/cb"},
RedirectURIs: []string{"https://foo.bar.com/oauth/cb"},
GrantMethod: GrantHandlerAuto,
ScopeRestrictions: []string{"full"},
},
wantErr: false,
expectURL: "https://foo.bar.com/oauth/err2",
wantErr: true,
expectURL: "https://foo.bar.com/oauth/cb2",
},
}

Expand Down
25 changes: 18 additions & 7 deletions pkg/apiserver/authentication/token/issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,30 @@ type Claims struct {
jwt.StandardClaims
// Private Claim Names
// TokenType defined the type of the token
TokenType Type `json:"token_type"`
// Username is user identity same as `sub`
Username string `json:"username"`
TokenType Type `json:"token_type,omitempty"`
// Username user identity, deprecated field
Username string `json:"username,omitempty"`
// Extra contains the additional information
Extra map[string][]string `json:"extra,omitempty"`

// Used for issuing authorization code
// Scopes can be used to request that specific sets of information be made available as Claim Values.
Scopes []string `json:"scopes,omitempty"`

// The following is well-known ID Token fields

// End-User's full name in displayable form including all name parts,
// possibly including titles and suffixes, ordered according to the End-User's locale and preferences.
Name string `json:"name,omitempty"`
// String value used to associate a Client session with an ID Token, and to mitigate replay attacks.
// The value is passed through unmodified from the Authentication Request to the ID Token.
Nonce string `json:"nonce,omitempty"`
// Scopes can be used to request that specific sets of information be made available as Claim Values.
Scopes []string `json:"scopes,omitempty"`
// End-User's preferred e-mail address.
Email string `json:"email,omitempty"`
// End-User's locale, represented as a BCP47 [RFC5646] language tag.
Locale string `json:"locale,omitempty"`
// Shorthand name by which the End-User wishes to be referred to at the RP,
PreferredUsername string `json:"preferred_username,omitempty"`
// Extra contains the additional information
Extra map[string][]string `json:"extra,omitempty"`
}

type issuer struct {
Expand Down Expand Up @@ -128,6 +136,9 @@ func (s *issuer) IssueTo(request *IssueRequest) (string, error) {
if len(request.Audience) > 0 {
claims.Audience = request.Audience
}
if request.Name != "" {
claims.Name = request.Name
}
if request.Nonce != "" {
claims.Nonce = request.Nonce
}
Expand Down
34 changes: 31 additions & 3 deletions pkg/kapis/oauth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ package oauth

import (
"fmt"
"gopkg.in/square/go-jose.v2"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"net/http"
"net/url"
"strings"
"time"

"gopkg.in/square/go-jose.v2"

"kubesphere.io/kubesphere/pkg/utils/sliceutil"

"github.com/form3tech-oss/jwt-go"

"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
Expand Down Expand Up @@ -443,7 +445,7 @@ func (h *handler) passwordGrant(username string, password string, req *restful.R
response.WriteHeaderAndEntity(http.StatusBadRequest, oauth.NewInvalidGrant(err))
return
case auth.RateLimitExceededError:
response.WriteHeaderAndEntity(http.StatusBadRequest, oauth.NewInvalidGrant(err))
response.WriteHeaderAndEntity(http.StatusTooManyRequests, oauth.NewInvalidGrant(err))
return
default:
response.WriteHeaderAndEntity(http.StatusInternalServerError, oauth.NewServerError(err))
Expand Down Expand Up @@ -602,6 +604,7 @@ func (h *handler) codeGrant(req *restful.Request, response *restful.Response) {
},
Nonce: authorizeContext.Nonce,
TokenType: token.IDToken,
Name: authorizeContext.User.GetName(),
},
ExpiresIn: h.options.OAuthOptions.AccessTokenMaxAge + h.options.OAuthOptions.AccessTokenInactivityTimeout,
}
Expand Down Expand Up @@ -655,3 +658,28 @@ func (h *handler) logout(req *restful.Request, resp *restful.Response) {
resp.Header().Set("Content-Type", "text/plain")
http.Redirect(resp, req.Request, redirectURL.String(), http.StatusFound)
}

// userinfo Endpoint is an OAuth 2.0 Protected Resource that returns Claims about the authenticated End-User.
func (h *handler) userinfo(req *restful.Request, response *restful.Response) {
authenticated, _ := request.UserFrom(req.Request.Context())
if authenticated == nil || authenticated.GetName() == user.Anonymous {
response.WriteHeaderAndEntity(http.StatusUnauthorized, oauth.ErrorLoginRequired)
return
}
detail, err := h.im.DescribeUser(authenticated.GetName())
if err != nil {
response.WriteHeaderAndEntity(http.StatusInternalServerError, oauth.NewServerError(err))
return
}

result := token.Claims{
StandardClaims: jwt.StandardClaims{
Subject: detail.Name,
},
Name: detail.Name,
Email: detail.Spec.Email,
Locale: detail.Spec.Lang,
PreferredUsername: detail.Name,
}
response.WriteEntity(result)
}
Loading

0 comments on commit 97326a8

Please sign in to comment.