This commit is contained in:
Livio Amstutz 2019-11-22 15:34:22 +01:00
parent 85b71e0867
commit d1d04295a6
12 changed files with 383 additions and 40 deletions

View file

@ -10,7 +10,7 @@ import (
str_utils "github.com/caos/utils/strings"
)
func ParseAuthRequest(w http.ResponseWriter, r *http.Request) (*oidc.AuthRequest, error) {
func Authorize(w http.ResponseWriter, r *http.Request, storage Storage) (*oidc.AuthRequest, error) {
err := r.ParseForm()
if err != nil {
return nil, errors.New("Unimplemented") //TODO: impl
@ -22,25 +22,53 @@ func ParseAuthRequest(w http.ResponseWriter, r *http.Request) (*oidc.AuthRequest
d.IgnoreUnknownKeys(true)
err = d.Decode(authReq, r.Form)
return authReq, err
if err != nil {
return nil, err
}
if err = ValidateAuthRequest(authReq, storage); err != nil {
return nil, err
}
err = storage.CreateAuthRequest(authReq)
if err != nil {
//TODO: return err
}
client, err := storage.GetClientByClientID(authReq.ClientID)
if err != nil {
return nil, err
}
RedirectToLogin(authReq, client, w, r)
return nil, nil
}
func ValidateAuthRequest(authRequest *oidc.AuthRequest, storage Storage) error {
if err := ValidateRedirectURI(authRequest.RedirectURI, authRequest.ClientID, storage); err != nil {
func ValidateAuthRequest(authReq *oidc.AuthRequest, storage Storage) error {
if err := ValidateAuthReqScopes(authReq.Scopes); err != nil {
return err
}
if err := ValidateAuthReqRedirectURI(authReq.RedirectURI, authReq.ClientID, storage); err != nil {
return err
}
return nil
return errors.New("Unimplemented") //TODO: impl https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.2
// if NeedsExistingSession(authRequest) {
// session, err := storage.CheckSession(authRequest)
// if err != nil {
// //TODO: return err
// //TODO: return err<
// }
// }
}
func ValidateRedirectURI(uri, client_id string, storage Storage) error {
func ValidateAuthReqScopes(scopes []string) error {
if len(scopes) == 0 {
return errors.New("scope missing")
}
if !str_utils.Contains(scopes, oidc.ScopeOpenID) {
return errors.New("scope openid missing")
}
return nil
}
func ValidateAuthReqRedirectURI(uri, client_id string, storage Storage) error {
if uri == "" {
return errors.New("redirect_uri must not be empty") //TODO:
}
@ -53,3 +81,8 @@ func ValidateRedirectURI(uri, client_id string, storage Storage) error {
}
return nil
}
func RedirectToLogin(authReq *oidc.AuthRequest, client oidc.Client, w http.ResponseWriter, r *http.Request) {
login := client.LoginURL(authReq.ID)
http.Redirect(w, r, login, http.StatusFound)
}

View file

@ -1,4 +1,4 @@
package op_test
package op
import (
"testing"
@ -51,7 +51,7 @@ func TestValidateAuthRequest(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := op.ValidateAuthRequest(tt.args.authRequest, tt.args.storage); (err != nil) != tt.wantErr {
if err := ValidateAuthRequest(tt.args.authRequest, tt.args.storage); (err != nil) != tt.wantErr {
t.Errorf("ValidateAuthRequest() error = %v, wantErr %v", err, tt.wantErr)
}
})
@ -102,7 +102,7 @@ func TestValidateRedirectURI(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := op.ValidateRedirectURI(tt.args.uri, tt.args.clientID, tt.args.storage); (err != nil) != tt.wantErr {
if err := ValidateAuthReqRedirectURI(tt.args.uri, tt.args.clientID, tt.args.storage); (err != nil) != tt.wantErr {
t.Errorf("ValidateRedirectURI() error = %v, wantErr %v", err, tt.wantErr)
}
})

View file

@ -1,6 +1,7 @@
package op
import (
"encoding/json"
"errors"
"net/http"
"net/url"
@ -180,22 +181,67 @@ func (p *DefaultOP) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
}
func (p *DefaultOP) HandleAuthorize(w http.ResponseWriter, r *http.Request) {
authRequest, err := ParseAuthRequest(w, r)
_, err := Authorize(w, r, p.storage)
if err != nil {
//TODO: return err
http.Error(w, err.Error(), 400)
}
err = ValidateAuthRequest(authRequest, p.storage)
if err != nil {
//TODO: return err
}
// err = p.storage.CreateAuthRequest(authRequest)
// authRequest, err := ParseAuthRequest(w, r)
// if err != nil {
// //TODO: return err
// }
//TODO: redirect?
// err = ValidateAuthRequest(authRequest, p.storage)
// if err != nil {
// http.Error(w, err.Error(), 400)
// return
// //TODO: return err
// }
// // err = p.storage.CreateAuthRequest(authRequest)
// // if err != nil {
// // //TODO: return err
// // }
// var client oidc.Client
// RedirectToLogin(authRequest, client, w, r)
}
func (p *DefaultOP) HandleExchange(w http.ResponseWriter, r *http.Request) {
reqType := r.FormValue("grant_type")
if reqType == "" {
//return errors.New("grant_type missing") //TODO: impl
}
if reqType == string(oidc.GrantTypeCode) {
token, err := CodeExchange(w, r, p.storage)
if err != nil {
}
b, _ := json.Marshal(token)
w.Write(b)
return
}
p.handleTokenExchange(w, r)
}
// func (p *DefaultOP) handleCodeExchange(w http.ResponseWriter, r *http.Request) {
// tokenRequest, err := ParseAccessTokenRequest(w, r)
// if err != nil {
// //TODO: return err
// }
// err = ValidateAccessTokenRequest(tokenRequest, p.storage)
// if err != nil {
// //TODO: return err
// }
// b, _ := json.Marshal(tokenRequest)
// w.Write(b)
// }
func (p *DefaultOP) handleTokenExchange(w http.ResponseWriter, r *http.Request) {
tokenRequest, err := ParseTokenExchangeRequest(w, r)
if err != nil {
//TODO: return err
}
err = ValidateTokenExchangeRequest(tokenRequest, p.storage)
if err != nil {
//TODO: return err
}
}
func (p *DefaultOP) HandleUserinfo(w http.ResponseWriter, r *http.Request) {

View file

@ -21,4 +21,5 @@ require (
github.com/gorilla/mux v1.7.3
github.com/gorilla/schema v1.1.0
github.com/stretchr/testify v1.4.0
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
)

View file

@ -6,7 +6,6 @@ package mock
import (
oidc "github.com/caos/oidc/pkg/oidc"
op "github.com/caos/oidc/pkg/op"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
@ -34,6 +33,51 @@ func (m *MockStorage) EXPECT() *MockStorageMockRecorder {
return m.recorder
}
// AuthRequestByCode mocks base method
func (m *MockStorage) AuthRequestByCode(arg0 oidc.Client, arg1, arg2 string) (*oidc.AuthRequest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthRequestByCode", arg0, arg1, arg2)
ret0, _ := ret[0].(*oidc.AuthRequest)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthRequestByCode indicates an expected call of AuthRequestByCode
func (mr *MockStorageMockRecorder) AuthRequestByCode(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthRequestByCode", reflect.TypeOf((*MockStorage)(nil).AuthRequestByCode), arg0, arg1, arg2)
}
// AuthorizeClientIDCodeVerifier mocks base method
func (m *MockStorage) AuthorizeClientIDCodeVerifier(arg0, arg1 string) (oidc.Client, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthorizeClientIDCodeVerifier", arg0, arg1)
ret0, _ := ret[0].(oidc.Client)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthorizeClientIDCodeVerifier indicates an expected call of AuthorizeClientIDCodeVerifier
func (mr *MockStorageMockRecorder) AuthorizeClientIDCodeVerifier(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthorizeClientIDCodeVerifier", reflect.TypeOf((*MockStorage)(nil).AuthorizeClientIDCodeVerifier), arg0, arg1)
}
// AuthorizeClientIDSecret mocks base method
func (m *MockStorage) AuthorizeClientIDSecret(arg0, arg1 string) (oidc.Client, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthorizeClientIDSecret", arg0, arg1)
ret0, _ := ret[0].(oidc.Client)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthorizeClientIDSecret indicates an expected call of AuthorizeClientIDSecret
func (mr *MockStorageMockRecorder) AuthorizeClientIDSecret(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthorizeClientIDSecret", reflect.TypeOf((*MockStorage)(nil).AuthorizeClientIDSecret), arg0, arg1)
}
// CreateAuthRequest mocks base method
func (m *MockStorage) CreateAuthRequest(arg0 *oidc.AuthRequest) error {
m.ctrl.T.Helper()
@ -48,11 +92,25 @@ func (mr *MockStorageMockRecorder) CreateAuthRequest(arg0 interface{}) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAuthRequest", reflect.TypeOf((*MockStorage)(nil).CreateAuthRequest), arg0)
}
// DeleteAuthRequestAndCode mocks base method
func (m *MockStorage) DeleteAuthRequestAndCode(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAuthRequestAndCode", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteAuthRequestAndCode indicates an expected call of DeleteAuthRequestAndCode
func (mr *MockStorageMockRecorder) DeleteAuthRequestAndCode(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAuthRequestAndCode", reflect.TypeOf((*MockStorage)(nil).DeleteAuthRequestAndCode), arg0, arg1)
}
// GetClientByClientID mocks base method
func (m *MockStorage) GetClientByClientID(arg0 string) (op.Client, error) {
func (m *MockStorage) GetClientByClientID(arg0 string) (oidc.Client, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetClientByClientID", arg0)
ret0, _ := ret[0].(op.Client)
ret0, _ := ret[0].(oidc.Client)
ret1, _ := ret[1].(error)
return ret0, ret1
}

View file

@ -5,6 +5,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op"
)
@ -18,6 +19,14 @@ func NewMockStorageExpectValidClientID(t *testing.T) op.Storage {
return m
}
func NewMockStorageAny(t *testing.T) op.Storage {
m := NewStorage(t)
mockS := m.(*MockStorage)
mockS.EXPECT().GetClientByClientID(gomock.Any()).AnyTimes().Return(&ConfClient{}, nil)
mockS.EXPECT().AuthorizeClientIDSecret(gomock.Any(), gomock.Any()).AnyTimes().Return(&ConfClient{}, nil)
return m
}
func ExpectValidClientID(s op.Storage) {
mockS := s.(*MockStorage)
mockS.EXPECT().GetClientByClientID(gomock.Any()).Return(&ConfClient{}, nil)
@ -25,8 +34,8 @@ func ExpectValidClientID(s op.Storage) {
type ConfClient struct{}
func (c *ConfClient) Type() op.ClientType {
return op.ClientTypeConfidential
func (c *ConfClient) Type() oidc.ClientType {
return oidc.ClientTypeConfidential
}
func (c *ConfClient) RedirectURIs() []string {
return []string{
@ -35,3 +44,7 @@ func (c *ConfClient) RedirectURIs() []string {
"custom://callback",
}
}
func (c *ConfClient) LoginURL(id string) string {
return "login?id=" + id
}

View file

@ -4,21 +4,9 @@ import "github.com/caos/oidc/pkg/oidc"
type Storage interface {
CreateAuthRequest(*oidc.AuthRequest) error
GetClientByClientID(string) (Client, error)
GetClientByClientID(string) (oidc.Client, error)
AuthRequestByCode(oidc.Client, string, string) (*oidc.AuthRequest, error)
AuthorizeClientIDSecret(string, string) (oidc.Client, error)
AuthorizeClientIDCodeVerifier(string, string) (oidc.Client, error)
DeleteAuthRequestAndCode(string, string) error
}
type Client interface {
RedirectURIs() []string
Type() ClientType
}
type ClientType int
func (c ClientType) IsConvidential() bool {
return c == ClientTypeConfidential
}
const (
ClientTypeConfidential ClientType = iota
ClientTypePublic
)

100
pkg/op/tokenrequest.go Normal file
View file

@ -0,0 +1,100 @@
package op
import (
"errors"
"net/http"
"github.com/gorilla/schema"
"github.com/caos/oidc/pkg/oidc"
)
// func ParseTokenRequest(w http.ResponseWriter, r *http.Request) (oidc.TokenRequest, error) {
// reqType := r.FormValue("grant_type")
// if reqType == "" {
// return nil, errors.New("grant_type missing") //TODO: impl
// }
// if reqType == string(oidc.GrantTypeCode) {
// return ParseAccessTokenRequest(w, r)
// }
// return ParseTokenExchangeRequest(w, r)
// }
func CodeExchange(w http.ResponseWriter, r *http.Request, storage Storage) (*oidc.AccessTokenResponse, error) {
err := r.ParseForm()
if err != nil {
return nil, errors.New("Unimplemented") //TODO: impl
}
tokenReq := new(oidc.AccessTokenRequest)
//TODO:
d := schema.NewDecoder()
d.IgnoreUnknownKeys(true)
err = d.Decode(tokenReq, r.Form)
if err != nil {
return nil, err
}
if tokenReq.Code == "" {
return nil, errors.New("code missing")
}
client, err := AuthorizeClient(r, tokenReq, storage)
if err != nil {
return nil, err
}
authReq, err := storage.AuthRequestByCode(client, tokenReq.Code, tokenReq.RedirectURI)
if err != nil {
}
err = storage.DeleteAuthRequestAndCode(authReq.ID, tokenReq.Code)
if err != nil {
}
accessToken, err := CreateAccessToken()
if err != nil {
}
idToken, err := CreateIDToken()
if err != nil {
}
return &oidc.AccessTokenResponse{
AccessToken: accessToken,
IDToken: idToken,
}, nil
}
func CreateAccessToken() (string, error) {
return "", nil
}
func CreateIDToken() (string, error) {
return "", nil
}
func AuthorizeClient(r *http.Request, tokenReq *oidc.AccessTokenRequest, storage Storage) (oidc.Client, error) {
if tokenReq.ClientID == "" {
clientID, clientSecret, ok := r.BasicAuth()
if ok {
return storage.AuthorizeClientIDSecret(clientID, clientSecret)
}
}
if tokenReq.ClientSecret != "" {
return storage.AuthorizeClientIDSecret(tokenReq.ClientID, tokenReq.ClientSecret)
}
if tokenReq.CodeVerifier != "" {
return storage.AuthorizeClientIDCodeVerifier(tokenReq.ClientID, tokenReq.CodeVerifier)
}
return nil, errors.New("Unimplemented") //TODO: impl
}
func ParseTokenExchangeRequest(w http.ResponseWriter, r *http.Request) (oidc.TokenRequest, error) {
return nil, errors.New("Unimplemented") //TODO: impl
}
func ValidateTokenExchangeRequest(tokenReq oidc.TokenRequest, storage Storage) error {
return errors.New("Unimplemented") //TODO: impl
}