baisc structure and server begin server impl
This commit is contained in:
parent
26bd873f4e
commit
f6ba7ab75e
17 changed files with 575 additions and 0 deletions
50
pkg/oidc/authorization.go
Normal file
50
pkg/oidc/authorization.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const (
|
||||
ResponseTypeCode = "code"
|
||||
ResponseTypeIDToken = "id_token token"
|
||||
ResponseTypeIDTokenOnly = "id_token"
|
||||
|
||||
DisplayPage = "page"
|
||||
DisplayPopup = "popup"
|
||||
DisplayTouch = "touch"
|
||||
DisplayWAP = "wap"
|
||||
|
||||
PromptNone = "none"
|
||||
PromptLogin = "login"
|
||||
PromptConsent = "consent"
|
||||
PromptSelectAccount = "select_account"
|
||||
)
|
||||
|
||||
//AuthRequest according to:
|
||||
//https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
//
|
||||
type AuthRequest struct {
|
||||
Scopes []string `schema:"scope"`
|
||||
ResponseType ResponseType `schema:"response_type"`
|
||||
ClientID string
|
||||
RedirectURI string //TODO: type
|
||||
|
||||
State string
|
||||
|
||||
// ResponseMode TODO: ?
|
||||
|
||||
Nonce string
|
||||
Display Display
|
||||
Prompt Prompt
|
||||
MaxAge uint32
|
||||
UILocales []language.Tag
|
||||
IDTokenHint string
|
||||
LoginHint string
|
||||
ACRValues []string
|
||||
}
|
||||
|
||||
type ResponseType string
|
||||
|
||||
type Display string
|
||||
|
||||
type Prompt string
|
33
pkg/oidc/client_credentials.go
Normal file
33
pkg/oidc/client_credentials.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package oidc
|
||||
|
||||
import "strings"
|
||||
|
||||
type clientCredentialsGrantBasic struct {
|
||||
grantType string `schema:"grant_type"`
|
||||
scope string `schema:"scope"`
|
||||
}
|
||||
|
||||
type clientCredentialsGrant struct {
|
||||
*clientCredentialsGrantBasic
|
||||
clientID string `schema:"client_id"`
|
||||
clientSecret string `schema:"client_secret"`
|
||||
}
|
||||
|
||||
//ClientCredentialsGrantBasic creates an oauth2 `Client Credentials` Grant
|
||||
//sneding client_id and client_secret as basic auth header
|
||||
func ClientCredentialsGrantBasic(scopes ...string) *clientCredentialsGrantBasic {
|
||||
return &clientCredentialsGrantBasic{
|
||||
grantType: "client_credentials",
|
||||
scope: strings.Join(scopes, " "),
|
||||
}
|
||||
}
|
||||
|
||||
//ClientCredentialsGrantValues creates an oauth2 `Client Credentials` Grant
|
||||
//sneding client_id and client_secret as form values
|
||||
func ClientCredentialsGrantValues(clientID, clientSecret string, scopes ...string) *clientCredentialsGrant {
|
||||
return &clientCredentialsGrant{
|
||||
clientCredentialsGrantBasic: ClientCredentialsGrantBasic(scopes...),
|
||||
clientID: clientID,
|
||||
clientSecret: clientSecret,
|
||||
}
|
||||
}
|
24
pkg/oidc/discovery.go
Normal file
24
pkg/oidc/discovery.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package oidc
|
||||
|
||||
const (
|
||||
DiscoveryEndpoint = "/.well-known/openid-configuration"
|
||||
)
|
||||
|
||||
type DiscoveryConfiguration struct {
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
|
||||
TokenEndpoint string `json:"token_endpoint,omitempty"`
|
||||
IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
|
||||
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
|
||||
CheckSessionIframe string `json:"check_session_iframe,omitempty"`
|
||||
JwksURI string `json:"jwks_uri,omitempty"`
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
||||
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
||||
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
|
||||
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
||||
}
|
84
pkg/oidc/token.go
Normal file
84
pkg/oidc/token.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
type IDTokenClaims struct {
|
||||
Issuer string `json:"iss,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
Audiences []string `json:"aud,omitempty"`
|
||||
Expiration time.Time `json:"exp,omitempty"`
|
||||
IssuedAt time.Time `json:"iat,omitempty"`
|
||||
AuthTime time.Time `json:"auth_time,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
AuthenticationContextClassReference string `json:"acr,omitempty"`
|
||||
AuthenticationMethodsReferences []string `json:"amr,omitempty"`
|
||||
AuthorizedParty string `json:"azp,omitempty"`
|
||||
AccessTokenHash string `json:"at_hash,omitempty"`
|
||||
|
||||
Signature jose.SignatureAlgorithm //TODO: ???
|
||||
}
|
||||
|
||||
func (t *IDTokenClaims) UnmarshalJSON(b []byte) error {
|
||||
var i jsonIDToken
|
||||
if err := json.Unmarshal(b, &i); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Issuer = i.Issuer
|
||||
t.Subject = i.Subject
|
||||
// t.Audiences = strings.Split(i.Audiences, " ")
|
||||
t.Audiences = i.Audiences
|
||||
t.Expiration = time.Unix(i.Expiration, 0).UTC()
|
||||
t.IssuedAt = time.Unix(i.IssuedAt, 0).UTC()
|
||||
t.AuthTime = time.Unix(i.AuthTime, 0).UTC()
|
||||
t.Nonce = i.Nonce
|
||||
t.AuthenticationContextClassReference = i.AuthenticationContextClassReference
|
||||
t.AuthenticationMethodsReferences = i.AuthenticationMethodsReferences
|
||||
t.AuthorizedParty = i.AuthorizedParty
|
||||
t.AccessTokenHash = i.AccessTokenHash
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *IDTokenClaims) MarshalJSON() ([]byte, error) {
|
||||
j := jsonIDToken{
|
||||
Issuer: t.Issuer,
|
||||
Subject: t.Subject,
|
||||
// Audiences: strings.Join(t.Audiences, " "),
|
||||
Audiences: t.Audiences,
|
||||
Expiration: t.Expiration.Unix(),
|
||||
IssuedAt: t.IssuedAt.Unix(),
|
||||
AuthTime: t.AuthTime.Unix(),
|
||||
Nonce: t.Nonce,
|
||||
AuthenticationContextClassReference: t.AuthenticationContextClassReference,
|
||||
AuthenticationMethodsReferences: t.AuthenticationMethodsReferences,
|
||||
AuthorizedParty: t.AuthorizedParty,
|
||||
AccessTokenHash: t.AccessTokenHash,
|
||||
}
|
||||
return json.Marshal(j)
|
||||
}
|
||||
|
||||
// type jsonTime time.Time
|
||||
|
||||
type jsonIDToken struct {
|
||||
Issuer string `json:"iss,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
Audiences []string `json:"aud,omitempty"`
|
||||
Expiration int64 `json:"exp,omitempty"`
|
||||
IssuedAt int64 `json:"iat,omitempty"`
|
||||
AuthTime int64 `json:"auth_time,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
AuthenticationContextClassReference string `json:"acr,omitempty"`
|
||||
AuthenticationMethodsReferences []string `json:"amr,omitempty"`
|
||||
AuthorizedParty string `json:"azp,omitempty"`
|
||||
AccessTokenHash string `json:"at_hash,omitempty"`
|
||||
}
|
||||
|
||||
type Tokens struct {
|
||||
*oauth2.Token
|
||||
IDTokenClaims *IDTokenClaims
|
||||
}
|
16
pkg/server/authrequest.go
Normal file
16
pkg/server/authrequest.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
func ParseAuthRequest(w http.ResponseWriter, r *http.Request) (*oidc.AuthRequest, error) {
|
||||
return nil, errors.New("Unimplemented") //TODO: impl
|
||||
}
|
||||
|
||||
func ValidateAuthRequest(authRequest *oidc.AuthRequest) error {
|
||||
return errors.New("Unimplemented") //TODO: impl https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.2
|
||||
}
|
9
pkg/server/config.go
Normal file
9
pkg/server/config.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package server
|
||||
|
||||
type Configuration interface {
|
||||
Issuer() string
|
||||
AuthorizationEndpoint() string
|
||||
TokenEndpoint() string
|
||||
UserinfoEndpoint() string
|
||||
Port() string
|
||||
}
|
101
pkg/server/default_handler.go
Normal file
101
pkg/server/default_handler.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/oidc/pkg/utils"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
type DefaultHandler struct {
|
||||
config *Config
|
||||
discoveryConfig *oidc.DiscoveryConfiguration
|
||||
storage Storage
|
||||
http *http.Server
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Issuer string
|
||||
AuthorizationEndpoint string
|
||||
TokenEndpoint string
|
||||
UserinfoEndpoint string
|
||||
Port string
|
||||
}
|
||||
|
||||
func (c *Config) OIDC() *oidc.DiscoveryConfiguration {
|
||||
return &oidc.DiscoveryConfiguration{}
|
||||
}
|
||||
|
||||
func NewDefaultHandler(config *Config, storage Storage) Handler {
|
||||
h := &DefaultHandler{
|
||||
config: config,
|
||||
discoveryConfig: config.OIDC(),
|
||||
storage: storage,
|
||||
}
|
||||
router := CreateRouter(h)
|
||||
h.http = &http.Server{
|
||||
Addr: config.Port,
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) Issuer() string {
|
||||
return h.config.Issuer
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) AuthorizationEndpoint() string {
|
||||
return h.config.AuthorizationEndpoint
|
||||
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) TokenEndpoint() string {
|
||||
return h.config.TokenEndpoint
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) UserinfoEndpoint() string {
|
||||
return h.config.UserinfoEndpoint
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) Port() string {
|
||||
return h.config.Port
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) HttpHandler() *http.Server {
|
||||
return h.http
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
|
||||
utils.MarshalJSON(w, h.discoveryConfig)
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request) {
|
||||
authRequest, err := ParseAuthRequest(w, r)
|
||||
if err != nil {
|
||||
//TODO: return err
|
||||
}
|
||||
err = ValidateAuthRequest(authRequest)
|
||||
if err != nil {
|
||||
//TODO: return err
|
||||
}
|
||||
if NeedsExistingSession(authRequest) {
|
||||
// session, err := h.storage.CheckSession(authRequest)
|
||||
// if err != nil {
|
||||
// //TODO: return err
|
||||
// }
|
||||
}
|
||||
err = h.storage.CreateAuthRequest(authRequest)
|
||||
if err != nil {
|
||||
//TODO: return err
|
||||
}
|
||||
//TODO: redirect?
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) HandleExchange(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) HandleUserinfo(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
47
pkg/server/default_handler_test.go
Normal file
47
pkg/server/default_handler_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
func TestDefaultHandler_HandleDiscovery(t *testing.T) {
|
||||
type fields struct {
|
||||
config *Config
|
||||
discoveryConfig *oidc.DiscoveryConfiguration
|
||||
storage Storage
|
||||
http *http.Server
|
||||
}
|
||||
type args struct {
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want string
|
||||
wantCode int
|
||||
}{
|
||||
{"OK", fields{config: nil, discoveryConfig: &oidc.DiscoveryConfiguration{Issuer: "test"}}, args{httptest.NewRecorder(), nil}, `{"issuer":"test"}`, 200},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := &DefaultHandler{
|
||||
config: tt.fields.config,
|
||||
discoveryConfig: tt.fields.discoveryConfig,
|
||||
storage: tt.fields.storage,
|
||||
http: tt.fields.http,
|
||||
}
|
||||
h.HandleDiscovery(tt.args.w, tt.args.r)
|
||||
rec := tt.args.w.(*httptest.ResponseRecorder)
|
||||
require.Equal(t, tt.want, rec.Body.String())
|
||||
require.Equal(t, tt.wantCode, rec.Code)
|
||||
})
|
||||
}
|
||||
}
|
44
pkg/server/handler.go
Normal file
44
pkg/server/handler.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/utils/logging"
|
||||
)
|
||||
|
||||
type Handler interface {
|
||||
Configuration
|
||||
// Storage() Storage
|
||||
HandleDiscovery(w http.ResponseWriter, r *http.Request)
|
||||
HandleAuthorize(w http.ResponseWriter, r *http.Request)
|
||||
HandleExchange(w http.ResponseWriter, r *http.Request)
|
||||
HandleUserinfo(w http.ResponseWriter, r *http.Request)
|
||||
HttpHandler() *http.Server
|
||||
}
|
||||
|
||||
func CreateRouter(h Handler) *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc(oidc.DiscoveryEndpoint, h.HandleDiscovery)
|
||||
router.HandleFunc(h.AuthorizationEndpoint(), h.HandleAuthorize)
|
||||
router.HandleFunc(h.TokenEndpoint(), h.HandleExchange)
|
||||
router.HandleFunc(h.UserinfoEndpoint(), h.HandleUserinfo)
|
||||
return router
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, h Handler) {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
err := h.HttpHandler().Shutdown(ctx)
|
||||
logging.Log("SERVE-REqwpM").OnError(err).Error("graceful shutdown of oidc server failed")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := h.HttpHandler().ListenAndServe()
|
||||
logging.Log("SERVE-4YNIwG").OnError(err).Panic("oidc server serve failed")
|
||||
}()
|
||||
logging.LogWithFields("SERVE-koAFMs", "port", h.Port()).Info("oidc server is listening")
|
||||
}
|
7
pkg/server/session.go
Normal file
7
pkg/server/session.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package server
|
||||
|
||||
import "github.com/caos/oidc/pkg/oidc"
|
||||
|
||||
func NeedsExistingSession(authRequest *oidc.AuthRequest) bool {
|
||||
return authRequest.IDTokenHint != "" //TODO: impl: https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.2
|
||||
}
|
7
pkg/server/storage.go
Normal file
7
pkg/server/storage.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package server
|
||||
|
||||
import "github.com/caos/oidc/pkg/oidc"
|
||||
|
||||
type Storage interface {
|
||||
CreateAuthRequest(*oidc.AuthRequest) error
|
||||
}
|
18
pkg/utils/marshal.go
Normal file
18
pkg/utils/marshal.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/utils/logging"
|
||||
)
|
||||
|
||||
func MarshalJSON(w http.ResponseWriter, i interface{}) {
|
||||
b, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
logging.Log("UTILS-zVu9OW").OnError(err).Error("error writing response")
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue