zitadel-oidc/pkg/op/authrequest.go
Livio Amstutz 8ee38d2ec8 authreq
2019-11-28 08:01:31 +01:00

171 lines
4.6 KiB
Go

package op
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/caos/oidc/pkg/oidc"
str_utils "github.com/caos/utils/strings"
)
type Authorizer interface {
Storage() Storage
Decoder() *schema.Decoder
Encoder() *schema.Encoder
Signer() Signer
}
type ValidationAuthorizer interface {
Authorizer
ValidateAuthRequest(*oidc.AuthRequest, Storage) error
}
func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
err := r.ParseForm()
if err != nil {
AuthRequestError(w, r, nil, ErrInvalidRequest("cannot parse form: %v", err))
return
}
authReq := new(oidc.AuthRequest)
err = authorizer.Decoder().Decode(authReq, r.Form)
if err != nil {
AuthRequestError(w, r, nil, ErrInvalidRequest(fmt.Sprintf("cannot parse auth request: %v", err)))
return
}
validation := ValidateAuthRequest
if validater, ok := authorizer.(ValidationAuthorizer); ok {
validation = validater.ValidateAuthRequest
}
if err := validation(authReq, authorizer.Storage()); err != nil {
AuthRequestError(w, r, authReq, err)
return
}
err = authorizer.Storage().CreateAuthRequest(authReq)
if err != nil {
AuthRequestError(w, r, authReq, err)
return
}
client, err := authorizer.Storage().GetClientByClientID(authReq.ClientID)
if err != nil {
AuthRequestError(w, r, authReq, err)
return
}
RedirectToLogin(authReq, client, w, r)
}
func ValidateAuthRequest(authReq *oidc.AuthRequest, storage Storage) error {
if err := ValidateAuthReqScopes(authReq.Scopes); err != nil {
return err
}
if err := ValidateAuthReqRedirectURI(authReq.RedirectURI, authReq.ClientID, authReq.ResponseType, 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<
// }
// }
}
func ValidateAuthReqScopes(scopes []string) error {
if len(scopes) == 0 {
return ErrInvalidRequest("scope missing")
}
if !str_utils.Contains(scopes, oidc.ScopeOpenID) {
return ErrInvalidRequest("scope openid missing")
}
return nil
}
func ValidateAuthReqRedirectURI(uri, client_id string, responseType oidc.ResponseType, storage Storage) error {
if uri == "" {
return ErrInvalidRequest("redirect_uri must not be empty")
}
client, err := storage.GetClientByClientID(client_id)
if err != nil {
return ErrServerError(err.Error())
}
if !str_utils.Contains(client.RedirectURIs(), uri) {
return ErrInvalidRequest("redirect_uri not allowed")
}
if strings.HasPrefix(uri, "https://") {
return nil
}
if responseType == oidc.ResponseTypeCode {
if strings.HasPrefix(uri, "http://") && oidc.IsConfidentialType(client) {
return nil
}
if client.ApplicationType() == oidc.ApplicationTypeNative {
return nil
}
return ErrInvalidRequest("redirect_uri not allowed 2")
} else {
if client.ApplicationType() != oidc.ApplicationTypeNative {
return ErrInvalidRequest("redirect_uri not allowed 3")
}
if !(strings.HasPrefix(uri, "http://localhost:") || strings.HasPrefix(uri, "http://localhost/")) {
return ErrInvalidRequest("redirect_uri not allowed 4")
}
}
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)
}
func AuthorizeCallback(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
params := mux.Vars(r)
id := params["id"]
authReq, err := authorizer.Storage().AuthRequestByID(id)
if err != nil {
AuthRequestError(w, r, nil, err)
return
}
AuthResponse(authReq, authorizer, w, r)
}
func AuthResponse(authReq *oidc.AuthRequest, authorizer Authorizer, w http.ResponseWriter, r *http.Request) {
var callback string
if authReq.ResponseType == oidc.ResponseTypeCode {
callback = fmt.Sprintf("%s?code=%s", authReq.RedirectURI, "test")
} else {
var accessToken string
var err error
if authReq.ResponseType != oidc.ResponseTypeIDTokenOnly {
accessToken, err = CreateAccessToken()
if err != nil {
}
}
idToken, err := CreateIDToken(authReq, accessToken, authorizer.Signer())
if err != nil {
}
resp := &oidc.AccessTokenResponse{
AccessToken: accessToken,
IDToken: idToken,
TokenType: "Bearer",
}
values := make(map[string][]string)
authorizer.Encoder().Encode(resp, values)
v := url.Values(values)
callback = fmt.Sprintf("%s#%s", authReq.RedirectURI, v.Encode())
}
http.Redirect(w, r, callback, http.StatusFound)
}