mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 19:07:30 +00:00
feat: add apis and application keys (#1327)
* feat: add apis and application keys * VerifyOIDCClientSecret * Update internal/v2/repository/project/api_config.go Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * Update internal/v2/repository/project/key.go Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * fix append ApplicationKeyWriteModel Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,10 @@ const (
|
||||
AppStateRemoved
|
||||
)
|
||||
|
||||
func (a AppState) Exists() bool {
|
||||
return !(a == AppStateUnspecified || a == AppStateRemoved)
|
||||
}
|
||||
|
||||
type ChangeApp struct {
|
||||
AppID string
|
||||
AppName string
|
||||
|
61
internal/v2/domain/application_api.go
Normal file
61
internal/v2/domain/application_api.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type APIApp struct {
|
||||
models.ObjectRoot
|
||||
|
||||
AppID string
|
||||
AppName string
|
||||
ClientID string
|
||||
ClientSecret *crypto.CryptoValue
|
||||
ClientSecretString string
|
||||
AuthMethodType APIAuthMethodType
|
||||
|
||||
State AppState
|
||||
}
|
||||
|
||||
func (a *APIApp) GetApplicationName() string {
|
||||
return a.AppName
|
||||
}
|
||||
|
||||
func (a *APIApp) GetState() AppState {
|
||||
return a.State
|
||||
}
|
||||
|
||||
type APIAuthMethodType int32
|
||||
|
||||
const (
|
||||
APIAuthMethodTypeBasic APIAuthMethodType = iota
|
||||
APIAuthMethodTypePrivateKeyJWT
|
||||
)
|
||||
|
||||
func (a *APIApp) IsValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *APIApp) setClientID(clientID string) {
|
||||
a.ClientID = clientID
|
||||
}
|
||||
|
||||
func (a *APIApp) setClientSecret(clientSecret *crypto.CryptoValue) {
|
||||
a.ClientSecret = clientSecret
|
||||
}
|
||||
|
||||
func (a *APIApp) requiresClientSecret() bool {
|
||||
return a.AuthMethodType == APIAuthMethodTypeBasic
|
||||
}
|
||||
|
||||
func (a *APIApp) GenerateClientSecretIfNeeded(generator crypto.Generator) (secret string, err error) {
|
||||
if a.AuthMethodType == APIAuthMethodTypePrivateKeyJWT {
|
||||
return "", nil
|
||||
}
|
||||
a.ClientSecret, secret, err = NewClientSecret(generator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return secret, nil
|
||||
}
|
60
internal/v2/domain/application_key.go
Normal file
60
internal/v2/domain/application_key.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type ApplicationKey struct {
|
||||
models.ObjectRoot
|
||||
|
||||
ApplicationID string
|
||||
ClientID string
|
||||
KeyID string
|
||||
Type AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
PrivateKey []byte
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) setPublicKey(publicKey []byte) {
|
||||
k.PublicKey = publicKey
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) setPrivateKey(privateKey []byte) {
|
||||
k.PrivateKey = privateKey
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) expirationDate() time.Time {
|
||||
return k.ExpirationDate
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) setExpirationDate(expiration time.Time) {
|
||||
k.ExpirationDate = expiration
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) Detail() ([]byte, error) {
|
||||
if k.Type == AuthNKeyTypeJSON {
|
||||
return k.MarshalJSON()
|
||||
}
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "KEY-dsg52", "Errors.Internal")
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Type string `json:"type"`
|
||||
KeyID string `json:"keyId"`
|
||||
Key string `json:"key"`
|
||||
AppID string `json:"appId"`
|
||||
ClientID string `json:"clientID"`
|
||||
}{
|
||||
Type: "application",
|
||||
KeyID: k.KeyID,
|
||||
Key: string(k.PrivateKey),
|
||||
AppID: k.ApplicationID,
|
||||
ClientID: k.ClientID,
|
||||
})
|
||||
}
|
50
internal/v2/domain/application_oauth.go
Normal file
50
internal/v2/domain/application_oauth.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
)
|
||||
|
||||
type oAuthApplication interface {
|
||||
setClientID(clientID string)
|
||||
setClientSecret(secret *crypto.CryptoValue)
|
||||
requiresClientSecret() bool
|
||||
}
|
||||
|
||||
//ClientID random_number@projectname (eg. 495894098234@zitadel)
|
||||
func SetNewClientID(a oAuthApplication, idGenerator id.Generator, project *Project) error {
|
||||
rndID, err := idGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.setClientID(fmt.Sprintf("%v@%v", rndID, strings.ReplaceAll(strings.ToLower(project.Name), " ", "_")))
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetNewClientSecretIfNeeded(a oAuthApplication, generator crypto.Generator) (string, error) {
|
||||
if !a.requiresClientSecret() {
|
||||
return "", nil
|
||||
}
|
||||
clientSecret, secretString, err := NewClientSecret(generator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
a.setClientSecret(clientSecret)
|
||||
return secretString, nil
|
||||
}
|
||||
|
||||
func NewClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) {
|
||||
cryptoValue, stringSecret, err := crypto.NewCode(generator)
|
||||
if err != nil {
|
||||
logging.Log("MODEL-UpnTI").OnError(err).Error("unable to create client secret")
|
||||
return nil, "", errors.ThrowInternal(err, "MODEL-gH2Wl", "Errors.Project.CouldNotGenerateClientSecret")
|
||||
}
|
||||
return cryptoValue, stringSecret, nil
|
||||
}
|
@@ -1,16 +1,11 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -46,7 +41,7 @@ type OIDCApp struct {
|
||||
State AppState
|
||||
}
|
||||
|
||||
func (h OIDCApp) GetUsername() string {
|
||||
func (h OIDCApp) GetApplicationName() string {
|
||||
return h.AppName
|
||||
}
|
||||
|
||||
@@ -54,6 +49,18 @@ func (h OIDCApp) GetState() AppState {
|
||||
return h.State
|
||||
}
|
||||
|
||||
func (h OIDCApp) setClientID(clientID string) {
|
||||
h.ClientID = clientID
|
||||
}
|
||||
|
||||
func (h OIDCApp) setClientSecret(clientSecret *crypto.CryptoValue) {
|
||||
h.ClientSecret = clientSecret
|
||||
}
|
||||
|
||||
func (h OIDCApp) requiresClientSecret() bool {
|
||||
return h.AuthMethodType == OIDCAuthMethodTypeBasic || h.AuthMethodType == OIDCAuthMethodTypePost
|
||||
}
|
||||
|
||||
type OIDCVersion int32
|
||||
|
||||
const (
|
||||
@@ -116,42 +123,6 @@ func (c *OIDCApp) IsValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
//ClientID random_number@projectname (eg. 495894098234@zitadel)
|
||||
func (c *OIDCApp) GenerateNewClientID(idGenerator id.Generator, project *Project) error {
|
||||
rndID, err := idGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.ClientID = fmt.Sprintf("%v@%v", rndID, strings.ReplaceAll(strings.ToLower(project.Name), " ", "_"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OIDCApp) GenerateClientSecretIfNeeded(generator crypto.Generator) (string, error) {
|
||||
if c.AuthMethodType == OIDCAuthMethodTypeNone {
|
||||
return "", nil
|
||||
}
|
||||
return c.GenerateNewClientSecret(generator)
|
||||
}
|
||||
|
||||
func (c *OIDCApp) GenerateNewClientSecret(generator crypto.Generator) (string, error) {
|
||||
cryptoValue, stringSecret, err := NewClientSecret(generator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.ClientSecret = cryptoValue
|
||||
return stringSecret, nil
|
||||
}
|
||||
|
||||
func NewClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) {
|
||||
cryptoValue, stringSecret, err := crypto.NewCode(generator)
|
||||
if err != nil {
|
||||
logging.Log("MODEL-UpnTI").OnError(err).Error("unable to create client secret")
|
||||
return nil, "", errors.ThrowInternal(err, "MODEL-gH2Wl", "Errors.Project.CouldNotGenerateClientSecret")
|
||||
}
|
||||
return cryptoValue, stringSecret, nil
|
||||
}
|
||||
|
||||
func (c *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
|
||||
grantTypes := make([]OIDCGrantType, 0)
|
||||
implicit := false
|
||||
|
86
internal/v2/domain/authn_key.go
Normal file
86
internal/v2/domain/authn_key.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
//most of us won't survive until 12-31-9999 23:59:59, maybe ZITADEL does
|
||||
defaultExpDate = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
|
||||
)
|
||||
|
||||
type AuthNKey interface {
|
||||
}
|
||||
|
||||
type authNKey interface {
|
||||
setPublicKey([]byte)
|
||||
setPrivateKey([]byte)
|
||||
expirationDate() time.Time
|
||||
setExpirationDate(time.Time)
|
||||
}
|
||||
|
||||
type AuthNKeyType int32
|
||||
|
||||
const (
|
||||
AuthNKeyTypeNONE = iota
|
||||
AuthNKeyTypeJSON
|
||||
|
||||
keyCount
|
||||
)
|
||||
|
||||
func (k AuthNKeyType) Valid() bool {
|
||||
return k >= 0 && k < keyCount
|
||||
}
|
||||
|
||||
func (key *MachineKey) GenerateNewMachineKeyPair(keySize int) error {
|
||||
privateKey, publicKey, err := crypto.GenerateKeyPair(keySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PublicKey, err = crypto.PublicKeyToBytes(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PrivateKey = crypto.PrivateKeyToBytes(privateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureValidExpirationDate(key authNKey) error {
|
||||
if key.expirationDate().IsZero() {
|
||||
key.setExpirationDate(defaultExpDate)
|
||||
}
|
||||
if key.expirationDate().Before(time.Now()) {
|
||||
return errors.ThrowInvalidArgument(nil, "AUTHN-dv3t5", "Errors.AuthNKey.ExpireBeforeNow")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetNewAuthNKeyPair(key authNKey, keySize int) error {
|
||||
privateKey, publicKey, err := NewAuthNKeyPair(keySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.setPrivateKey(privateKey)
|
||||
key.setPublicKey(publicKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAuthNKeyPair(keySize int) (privateKey, publicKey []byte, err error) {
|
||||
private, public, err := crypto.GenerateKeyPair(keySize)
|
||||
if err != nil {
|
||||
logging.Log("AUTHN-Ud51I").WithError(err).Error("unable to create authn key pair")
|
||||
return nil, nil, errors.ThrowInternal(err, "AUTHN-gdg2l", "Errors.Project.CouldNotGenerateClientSecret")
|
||||
}
|
||||
publicKey, err = crypto.PublicKeyToBytes(public)
|
||||
if err != nil {
|
||||
logging.Log("AUTHN-Dbb35").WithError(err).Error("unable to convert public key")
|
||||
return nil, nil, errors.ThrowInternal(err, "AUTHN-Bne3f", "Errors.Project.CouldNotGenerateClientSecret")
|
||||
}
|
||||
privateKey = crypto.PrivateKeyToBytes(private)
|
||||
return privateKey, publicKey, nil
|
||||
}
|
@@ -1,29 +1,36 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type MachineKey struct {
|
||||
models.ObjectRoot
|
||||
|
||||
KeyID string
|
||||
Type MachineKeyType
|
||||
Type AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
PrivateKey []byte
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
type MachineKeyType int32
|
||||
func (key *MachineKey) setPublicKey(publicKey []byte) {
|
||||
key.PublicKey = publicKey
|
||||
}
|
||||
|
||||
const (
|
||||
MachineKeyTypeNONE = iota
|
||||
MachineKeyTypeJSON
|
||||
func (key *MachineKey) setPrivateKey(privateKey []byte) {
|
||||
key.PrivateKey = privateKey
|
||||
}
|
||||
|
||||
keyCount
|
||||
)
|
||||
func (key *MachineKey) expirationDate() time.Time {
|
||||
return key.ExpirationDate
|
||||
}
|
||||
|
||||
func (key *MachineKey) setExpirationDate(expiration time.Time) {
|
||||
key.ExpirationDate = expiration
|
||||
}
|
||||
|
||||
type MachineKeyState int32
|
||||
|
||||
@@ -38,20 +45,3 @@ const (
|
||||
func (f MachineKeyState) Valid() bool {
|
||||
return f >= 0 && f < machineKeyStateCount
|
||||
}
|
||||
|
||||
func (f MachineKeyType) Valid() bool {
|
||||
return f >= 0 && f < keyCount
|
||||
}
|
||||
|
||||
func (key *MachineKey) GenerateNewMachineKeyPair(keySize int) error {
|
||||
privateKey, publicKey, err := crypto.GenerateKeyPair(keySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PublicKey, err = crypto.PublicKeyToBytes(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PrivateKey = crypto.PrivateKeyToBytes(privateKey)
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user