1
0
mirror of https://github.com/pocket-id/pocket-id.git synced 2026-02-09 14:54:20 +00:00

feat: add support for SCIM provisioning (#1182)

This commit is contained in:
Elias Schneider
2026-01-02 17:54:20 +01:00
committed by GitHub
parent e4a8ca476c
commit 579cfdc678
37 changed files with 1963 additions and 34 deletions

View File

@@ -0,0 +1,14 @@
package model
import datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
type ScimServiceProvider struct {
Base
Endpoint string `sortable:"true"`
Token datatype.EncryptedString
LastSyncedAt *datatype.DateTime `sortable:"true"`
OidcClientID string
OidcClient OidcClient `gorm:"foreignKey:OidcClientID;references:ID;"`
}

View File

@@ -0,0 +1,91 @@
package datatype
import (
"crypto/sha256"
"database/sql/driver"
"encoding/base64"
"fmt"
"io"
"github.com/pocket-id/pocket-id/backend/internal/common"
cryptoutils "github.com/pocket-id/pocket-id/backend/internal/utils/crypto"
"golang.org/x/crypto/hkdf"
)
const encryptedStringAAD = "encrypted_string"
var encStringKey []byte
// EncryptedString stores plaintext in memory and persists encrypted data in the database.
type EncryptedString string //nolint:recvcheck
func (e *EncryptedString) Scan(value any) error {
if value == nil {
*e = ""
return nil
}
var raw string
switch v := value.(type) {
case string:
raw = v
case []byte:
raw = string(v)
default:
return fmt.Errorf("unexpected type for EncryptedString: %T", value)
}
if raw == "" {
*e = ""
return nil
}
encBytes, err := base64.StdEncoding.DecodeString(raw)
if err != nil {
return fmt.Errorf("failed to decode encrypted string: %w", err)
}
decBytes, err := cryptoutils.Decrypt(encStringKey, encBytes, []byte(encryptedStringAAD))
if err != nil {
return fmt.Errorf("failed to decrypt encrypted string: %w", err)
}
*e = EncryptedString(decBytes)
return nil
}
func (e EncryptedString) Value() (driver.Value, error) {
if e == "" {
return "", nil
}
encBytes, err := cryptoutils.Encrypt(encStringKey, []byte(e), []byte(encryptedStringAAD))
if err != nil {
return nil, fmt.Errorf("failed to encrypt string: %w", err)
}
return base64.StdEncoding.EncodeToString(encBytes), nil
}
func (e EncryptedString) String() string {
return string(e)
}
func deriveEncryptedStringKey(master []byte) ([]byte, error) {
const info = "pocketid/encrypted_string"
r := hkdf.New(sha256.New, master, nil, []byte(info))
key := make([]byte, 32)
if _, err := io.ReadFull(r, key); err != nil {
return nil, err
}
return key, nil
}
func init() {
key, err := deriveEncryptedStringKey(common.EnvConfig.EncryptionKey)
if err != nil {
panic(fmt.Sprintf("failed to derive encrypted string key: %v", err))
}
encStringKey = key
}

View File

@@ -2,6 +2,7 @@ package model
import (
"strings"
"time"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
@@ -22,6 +23,7 @@ type User struct {
Locale *string
LdapID *string
Disabled bool `sortable:"true" filterable:"true"`
UpdatedAt *datatype.DateTime
CustomClaims []CustomClaim
UserGroups []UserGroup `gorm:"many2many:user_groups_users;"`
@@ -85,6 +87,13 @@ func (u User) Initials() string {
return strings.ToUpper(first + last)
}
func (u User) LastModified() time.Time {
if u.UpdatedAt != nil {
return u.UpdatedAt.ToTime()
}
return u.CreatedAt.ToTime()
}
type OneTimeAccessToken struct {
Base
Token string

View File

@@ -1,11 +1,25 @@
package model
import (
"time"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
)
type UserGroup struct {
Base
FriendlyName string `sortable:"true"`
Name string `sortable:"true"`
LdapID *string
UpdatedAt *datatype.DateTime
Users []User `gorm:"many2many:user_groups_users;"`
CustomClaims []CustomClaim
AllowedOidcClients []OidcClient `gorm:"many2many:oidc_clients_allowed_user_groups;"`
}
func (ug UserGroup) LastModified() time.Time {
if ug.UpdatedAt != nil {
return ug.UpdatedAt.ToTime()
}
return ug.CreatedAt.ToTime()
}