zitadel/internal/api/oidc/client.go
2020-09-21 12:55:39 +02:00

152 lines
4.2 KiB
Go

package oidc
import (
"context"
"golang.org/x/text/language"
"gopkg.in/square/go-jose.v2"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
proj_model "github.com/caos/zitadel/internal/project/model"
user_model "github.com/caos/zitadel/internal/user/model"
)
const (
scopeOpenID = "openid"
scopeProfile = "profile"
scopeEmail = "email"
scopePhone = "phone"
scopeAddress = "address"
oidcCtx = "oidc"
)
func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (op.Client, error) {
client, err := o.repo.ApplicationByClientID(ctx, id)
if err != nil {
return nil, err
}
if client.State != proj_model.AppStateActive {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sdaGg", "client is not active")
}
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime)
}
func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error) {
key, err := o.repo.MachineKeyByID(ctx, keyID)
if err != nil {
return nil, err
}
if key.UserID != userID {
return nil, errors.ThrowPermissionDenied(nil, "OIDC-24jm3", "key from different user")
}
publicKey, err := crypto.BytesToPublicKey(key.PublicKey)
if err != nil {
return nil, err
}
return &jose.JSONWebKey{
KeyID: key.ID,
Use: "sig",
Key: publicKey,
}, nil
}
func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secret string) error {
ctx = authz.SetCtxData(ctx, authz.CtxData{
UserID: oidcCtx,
OrgID: oidcCtx,
})
return o.repo.AuthorizeOIDCApplication(ctx, id, secret)
}
func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, origin string) (*oidc.Userinfo, error) {
token, err := o.repo.TokenByID(ctx, tokenID)
if err != nil {
return nil, err
}
if token.ApplicationID != "" {
app, err := o.repo.ApplicationByClientID(ctx, token.ApplicationID)
if err != nil {
return nil, err
}
if origin != "" && !http.IsOriginAllowed(app.OriginAllowList, origin) {
return nil, errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
}
}
return o.GetUserinfoFromScopes(ctx, token.UserID, token.Scopes)
}
func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID string, scopes []string) (*oidc.Userinfo, error) {
user, err := o.repo.UserByID(ctx, userID)
if err != nil {
return nil, err
}
userInfo := new(oidc.Userinfo)
for _, scope := range scopes {
switch scope {
case scopeOpenID:
userInfo.Subject = user.ID
case scopeEmail:
if user.HumanView == nil {
continue
}
userInfo.Email = user.Email
userInfo.EmailVerified = user.IsEmailVerified
case scopeProfile:
userInfo.PreferredUsername = user.PreferredLoginName
userInfo.UpdatedAt = user.ChangeDate
if user.HumanView != nil {
userInfo.Name = user.DisplayName
userInfo.FamilyName = user.LastName
userInfo.GivenName = user.FirstName
userInfo.Nickname = user.NickName
userInfo.Gender = oidc.Gender(getGender(user.Gender))
userInfo.Locale, err = language.Parse(user.PreferredLanguage)
} else {
userInfo.Name = user.MachineView.Name
}
case scopePhone:
if user.HumanView == nil {
continue
}
userInfo.PhoneNumber = user.Phone
userInfo.PhoneNumberVerified = user.IsPhoneVerified
case scopeAddress:
if user.HumanView == nil {
continue
}
if user.StreetAddress == "" && user.Locality == "" && user.Region == "" && user.PostalCode == "" && user.Country == "" {
continue
}
userInfo.Address = &oidc.UserinfoAddress{
StreetAddress: user.StreetAddress,
Locality: user.Locality,
Region: user.Region,
PostalCode: user.PostalCode,
Country: user.Country,
}
default:
userInfo.Authorizations = append(userInfo.Authorizations, scope)
}
}
return userInfo, nil
}
func getGender(gender user_model.Gender) string {
switch gender {
case user_model.GenderFemale:
return "female"
case user_model.GenderMale:
return "male"
case user_model.GenderDiverse:
return "diverse"
}
return ""
}