WIP: designing interfaces, detecting identity, enforcing policies
This commit is contained in:
parent
e9caa1b033
commit
8f1ff2d1a2
@ -25,6 +25,7 @@ import (
|
||||
httpfs "github.com/datarhei/core/v16/http/fs"
|
||||
"github.com/datarhei/core/v16/http/jwt"
|
||||
"github.com/datarhei/core/v16/http/router"
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
"github.com/datarhei/core/v16/io/fs"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
"github.com/datarhei/core/v16/math/rand"
|
||||
@ -83,6 +84,7 @@ type api struct {
|
||||
httpjwt jwt.JWT
|
||||
update update.Checker
|
||||
replacer replace.Replacer
|
||||
iam iam.IAM
|
||||
|
||||
errorChan chan error
|
||||
|
||||
@ -381,6 +383,13 @@ func (a *api) start() error {
|
||||
a.sessions = sessions
|
||||
}
|
||||
|
||||
iam, err := iam.NewIAM()
|
||||
if err != nil {
|
||||
return fmt.Errorf("iam: %w", err)
|
||||
}
|
||||
|
||||
a.iam = iam
|
||||
|
||||
diskfs, err := fs.NewRootedDiskFilesystem(fs.RootedDiskConfig{
|
||||
Root: cfg.Storage.Disk.Dir,
|
||||
Logger: a.log.logger.core.WithComponent("DiskFS"),
|
||||
@ -874,6 +883,7 @@ func (a *api) start() error {
|
||||
Token: cfg.RTMP.Token,
|
||||
Logger: a.log.logger.rtmp,
|
||||
Collector: a.sessions.Collector("rtmp"),
|
||||
IAM: a.iam,
|
||||
}
|
||||
|
||||
if cfg.RTMP.EnableTLS {
|
||||
@ -902,6 +912,7 @@ func (a *api) start() error {
|
||||
Token: cfg.SRT.Token,
|
||||
Logger: a.log.logger.core.WithComponent("SRT").WithField("address", cfg.SRT.Address),
|
||||
Collector: a.sessions.Collector("srt"),
|
||||
IAM: a.iam,
|
||||
}
|
||||
|
||||
if cfg.SRT.Log.Enable {
|
||||
@ -1012,6 +1023,7 @@ func (a *api) start() error {
|
||||
Sessions: a.sessions,
|
||||
Router: router,
|
||||
ReadOnly: cfg.API.ReadOnly,
|
||||
IAM: a.iam,
|
||||
}
|
||||
|
||||
mainserverhandler, err := http.NewServer(serverConfig)
|
||||
|
||||
@ -92,7 +92,19 @@ func New(config Config) (JWT, error) {
|
||||
AuthScheme: "Bearer",
|
||||
Claims: jwtgo.MapClaims{},
|
||||
ErrorHandlerWithContext: j.ErrorHandler,
|
||||
ParseTokenFunc: j.parseToken("access"),
|
||||
SuccessHandler: func(c echo.Context) {
|
||||
token := c.Get("user").(*jwtgo.Token)
|
||||
|
||||
var subject string
|
||||
if claims, ok := token.Claims.(jwtgo.MapClaims); ok {
|
||||
if sub, ok := claims["sub"]; ok {
|
||||
subject = sub.(string)
|
||||
}
|
||||
}
|
||||
|
||||
c.Set("user", subject)
|
||||
},
|
||||
ParseTokenFunc: j.parseToken("access"),
|
||||
}
|
||||
|
||||
j.refreshConfig = middleware.JWTConfig{
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
"github.com/datarhei/core/v16/http/handler/util"
|
||||
"github.com/datarhei/core/v16/http/jwt/jwks"
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
|
||||
jwtgo "github.com/golang-jwt/jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
@ -24,14 +24,12 @@ type Validator interface {
|
||||
}
|
||||
|
||||
type localValidator struct {
|
||||
username string
|
||||
password string
|
||||
iam iam.IAM
|
||||
}
|
||||
|
||||
func NewLocalValidator(username, password string) (Validator, error) {
|
||||
func NewLocalValidator(iam iam.IAM) (Validator, error) {
|
||||
v := &localValidator{
|
||||
username: username,
|
||||
password: password,
|
||||
iam: iam,
|
||||
}
|
||||
|
||||
return v, nil
|
||||
@ -48,46 +46,34 @@ func (v *localValidator) Validate(c echo.Context) (bool, string, error) {
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
if login.Username != v.username || login.Password != v.password {
|
||||
identity := v.iam.GetIdentity(login.Username)
|
||||
if identity == nil {
|
||||
return true, "", fmt.Errorf("invalid username or password")
|
||||
}
|
||||
|
||||
return true, v.username, nil
|
||||
if !identity.VerifyAPIPassword(login.Password) {
|
||||
return true, "", fmt.Errorf("invalid username or password")
|
||||
}
|
||||
|
||||
return true, login.Username, nil
|
||||
}
|
||||
|
||||
func (v *localValidator) Cancel() {}
|
||||
|
||||
type auth0Validator struct {
|
||||
domain string
|
||||
issuer string
|
||||
audience string
|
||||
clientID string
|
||||
users []string
|
||||
certs jwks.JWKS
|
||||
iam iam.IAM
|
||||
}
|
||||
|
||||
func NewAuth0Validator(domain, audience, clientID string, users []string) (Validator, error) {
|
||||
func NewAuth0Validator(iam iam.IAM) (Validator, error) {
|
||||
v := &auth0Validator{
|
||||
domain: domain,
|
||||
issuer: "https://" + domain + "/",
|
||||
audience: audience,
|
||||
clientID: clientID,
|
||||
users: users,
|
||||
iam: iam,
|
||||
}
|
||||
|
||||
url := v.issuer + ".well-known/jwks.json"
|
||||
certs, err := jwks.NewFromURL(url, jwks.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.certs = certs
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (v auth0Validator) String() string {
|
||||
return fmt.Sprintf("auth0 domain=%s audience=%s clientid=%s", v.domain, v.audience, v.clientID)
|
||||
return fmt.Sprintf("auth0 domain=%s audience=%s clientid=%s", "", "", "")
|
||||
}
|
||||
|
||||
func (v *auth0Validator) Validate(c echo.Context) (bool, string, error) {
|
||||
@ -116,26 +102,6 @@ func (v *auth0Validator) Validate(c echo.Context) (bool, string, error) {
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
var issuer string
|
||||
if claims, ok := token.Claims.(jwtgo.MapClaims); ok {
|
||||
if iss, ok := claims["iss"]; ok {
|
||||
issuer = iss.(string)
|
||||
}
|
||||
}
|
||||
|
||||
if issuer != v.issuer {
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
token, err = jwtgo.Parse(auth, v.keyFunc)
|
||||
if err != nil {
|
||||
return true, "", err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return true, "", fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
var subject string
|
||||
if claims, ok := token.Claims.(jwtgo.MapClaims); ok {
|
||||
if sub, ok := claims["sub"]; ok {
|
||||
@ -143,72 +109,16 @@ func (v *auth0Validator) Validate(c echo.Context) (bool, string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
identity := v.iam.GetIdentityByAuth0(subject)
|
||||
if identity == nil {
|
||||
return true, "", fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
if !identity.VerifyAPIAuth0(auth) {
|
||||
return true, "", fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
return true, subject, nil
|
||||
}
|
||||
|
||||
func (v *auth0Validator) keyFunc(token *jwtgo.Token) (interface{}, error) {
|
||||
// Verify 'aud' claim
|
||||
checkAud := token.Claims.(jwtgo.MapClaims).VerifyAudience(v.audience, false)
|
||||
if !checkAud {
|
||||
return nil, fmt.Errorf("invalid audience")
|
||||
}
|
||||
|
||||
// Verify 'iss' claim
|
||||
checkIss := token.Claims.(jwtgo.MapClaims).VerifyIssuer(v.issuer, false)
|
||||
if !checkIss {
|
||||
return nil, fmt.Errorf("invalid issuer")
|
||||
}
|
||||
|
||||
// Verify 'sub' claim
|
||||
if _, ok := token.Claims.(jwtgo.MapClaims)["sub"]; !ok {
|
||||
return nil, fmt.Errorf("sub claim is required")
|
||||
}
|
||||
|
||||
sub := token.Claims.(jwtgo.MapClaims)["sub"].(string)
|
||||
found := false
|
||||
for _, u := range v.users {
|
||||
if sub == u {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("user not allowed")
|
||||
}
|
||||
|
||||
// find the key
|
||||
if _, ok := token.Header["kid"]; !ok {
|
||||
return nil, fmt.Errorf("kid not found")
|
||||
}
|
||||
|
||||
kid := token.Header["kid"].(string)
|
||||
|
||||
key, err := v.certs.Key(kid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no cert for kid found: %w", err)
|
||||
}
|
||||
|
||||
// find algorithm
|
||||
if _, ok := token.Header["alg"]; !ok {
|
||||
return nil, fmt.Errorf("kid not found")
|
||||
}
|
||||
|
||||
alg := token.Header["alg"].(string)
|
||||
|
||||
if key.Alg() != alg {
|
||||
return nil, fmt.Errorf("signing method doesn't match")
|
||||
}
|
||||
|
||||
// get the public key
|
||||
publicKey, err := key.PublicKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid public key: %w", err)
|
||||
}
|
||||
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
func (v *auth0Validator) Cancel() {
|
||||
v.certs.Cancel()
|
||||
}
|
||||
func (v *auth0Validator) Cancel() {}
|
||||
|
||||
60
http/middleware/iam/iam.go
Normal file
60
http/middleware/iam/iam.go
Normal file
@ -0,0 +1,60 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper middleware.Skipper
|
||||
IAM iam.IAM
|
||||
}
|
||||
|
||||
var DefaultConfig = Config{
|
||||
Skipper: middleware.DefaultSkipper,
|
||||
IAM: nil,
|
||||
}
|
||||
|
||||
func New() echo.MiddlewareFunc {
|
||||
return NewWithConfig(DefaultConfig)
|
||||
}
|
||||
|
||||
func NewWithConfig(config Config) echo.MiddlewareFunc {
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
if config.IAM == nil {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
user := c.Get("user").(string)
|
||||
if len(user) == 0 {
|
||||
user = "$anon"
|
||||
}
|
||||
domain := c.QueryParam("group")
|
||||
if len(domain) == 0 {
|
||||
domain = "$none"
|
||||
}
|
||||
resource := c.Request().URL.Path
|
||||
action := c.Request().Method
|
||||
|
||||
if !config.IAM.Enforce(user, domain, resource, action) {
|
||||
return echo.NewHTTPError(http.StatusForbidden)
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,7 @@ import (
|
||||
"github.com/datarhei/core/v16/http/jwt"
|
||||
"github.com/datarhei/core/v16/http/router"
|
||||
"github.com/datarhei/core/v16/http/validator"
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
"github.com/datarhei/core/v16/monitor"
|
||||
"github.com/datarhei/core/v16/net"
|
||||
@ -92,6 +93,7 @@ type Config struct {
|
||||
Sessions session.RegistryReader
|
||||
Router router.Router
|
||||
ReadOnly bool
|
||||
IAM iam.IAM
|
||||
}
|
||||
|
||||
type CorsConfig struct {
|
||||
|
||||
21
iam/access.go
Normal file
21
iam/access.go
Normal file
@ -0,0 +1,21 @@
|
||||
package iam
|
||||
|
||||
type AccessEnforcer interface {
|
||||
Enforce(name, domain, resource, action string) bool
|
||||
}
|
||||
|
||||
type AccessManager interface {
|
||||
AccessEnforcer
|
||||
|
||||
AddPolicy()
|
||||
}
|
||||
|
||||
type access struct {
|
||||
}
|
||||
|
||||
func NewAccessManager() (AccessManager, error) {
|
||||
return &access{}, nil
|
||||
}
|
||||
|
||||
func (a *access) AddPolicy() {}
|
||||
func (a *access) Enforce(name, domain, resource, action string) bool { return false }
|
||||
26
iam/iam.go
Normal file
26
iam/iam.go
Normal file
@ -0,0 +1,26 @@
|
||||
package iam
|
||||
|
||||
type IAM interface {
|
||||
Enforce(user, domain, resource, action string) bool
|
||||
|
||||
GetIdentity(name string) IdentityVerifier
|
||||
GetIdentityByAuth0(name string) IdentityVerifier
|
||||
}
|
||||
|
||||
type iam struct{}
|
||||
|
||||
func NewIAM() (IAM, error) {
|
||||
return &iam{}, nil
|
||||
}
|
||||
|
||||
func (i *iam) Enforce(user, domain, resource, action string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *iam) GetIdentity(name string) IdentityVerifier {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *iam) GetIdentityByAuth0(name string) IdentityVerifier {
|
||||
return nil
|
||||
}
|
||||
440
iam/identity.go
Normal file
440
iam/identity.go
Normal file
@ -0,0 +1,440 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/datarhei/core/v16/http/jwt/jwks"
|
||||
|
||||
jwtgo "github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
// Auth0
|
||||
// there needs to be a mapping from the Auth.User to Name
|
||||
// the same Auth0.User can't have multiple identities
|
||||
// the whole jwks will be part of this package
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Superuser bool `json:"superuser"`
|
||||
Auth struct {
|
||||
API struct {
|
||||
Userpass struct {
|
||||
Enable bool `json:"enable"`
|
||||
Password string `json:"password"`
|
||||
} `json:"userpass"`
|
||||
Auth0 struct {
|
||||
Enable bool `json:"enable"`
|
||||
User string `json:"user"`
|
||||
Tenant Auth0Tenant `json:"tenant"`
|
||||
} `json:"auth0"`
|
||||
} `json:"api"`
|
||||
Services struct {
|
||||
Basic struct {
|
||||
Enable bool `json:"enable"`
|
||||
Password string `json:"password"`
|
||||
} `json:"basic"`
|
||||
Token string `json:"token"`
|
||||
} `json:"services"`
|
||||
} `json:"auth"`
|
||||
}
|
||||
|
||||
func (u *User) validate() error {
|
||||
if len(u.Name) == 0 {
|
||||
return fmt.Errorf("the name is required")
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`[^A-Za-z0-9_-]`)
|
||||
if re.MatchString(u.Name) {
|
||||
return fmt.Errorf("the name can only the contain [A-Za-z0-9_-]")
|
||||
}
|
||||
|
||||
if u.Auth.API.Userpass.Enable && len(u.Auth.API.Userpass.Password) == 0 {
|
||||
return fmt.Errorf("a password for API login is required")
|
||||
}
|
||||
|
||||
if u.Auth.API.Auth0.Enable && len(u.Auth.API.Auth0.User) == 0 {
|
||||
return fmt.Errorf("a user for Auth0 login is required")
|
||||
}
|
||||
|
||||
if u.Auth.Services.Basic.Enable && len(u.Auth.Services.Basic.Password) == 0 {
|
||||
return fmt.Errorf("a password for service basic auth is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) marshalIdentity() *identity {
|
||||
i := &identity{
|
||||
user: *u,
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
type identity struct {
|
||||
user User
|
||||
|
||||
tenant *auth0Tenant
|
||||
|
||||
valid bool
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (i *identity) VerifyAPIPassword(password string) bool {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
|
||||
if !i.isValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
if !i.user.Auth.API.Userpass.Enable {
|
||||
return false
|
||||
}
|
||||
|
||||
return i.user.Auth.API.Userpass.Password == password
|
||||
}
|
||||
|
||||
func (i *identity) VerifyAPIAuth0(jwt string) bool {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
|
||||
if !i.isValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
if !i.user.Auth.API.Auth0.Enable {
|
||||
return false
|
||||
}
|
||||
|
||||
p := &jwtgo.Parser{}
|
||||
token, _, err := p.ParseUnverified(jwt, jwtgo.MapClaims{})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var subject string
|
||||
if claims, ok := token.Claims.(jwtgo.MapClaims); ok {
|
||||
if sub, ok := claims["sub"]; ok {
|
||||
subject = sub.(string)
|
||||
}
|
||||
}
|
||||
|
||||
if subject != i.user.Auth.API.Auth0.User {
|
||||
return false
|
||||
}
|
||||
|
||||
var issuer string
|
||||
if claims, ok := token.Claims.(jwtgo.MapClaims); ok {
|
||||
if iss, ok := claims["iss"]; ok {
|
||||
issuer = iss.(string)
|
||||
}
|
||||
}
|
||||
|
||||
if issuer != i.tenant.issuer {
|
||||
return false
|
||||
}
|
||||
|
||||
token, err = jwtgo.Parse(jwt, i.auth0KeyFunc)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *identity) auth0KeyFunc(token *jwtgo.Token) (interface{}, error) {
|
||||
// Verify 'aud' claim
|
||||
checkAud := token.Claims.(jwtgo.MapClaims).VerifyAudience(i.tenant.audience, false)
|
||||
if !checkAud {
|
||||
return nil, fmt.Errorf("invalid audience")
|
||||
}
|
||||
|
||||
// Verify 'iss' claim
|
||||
checkIss := token.Claims.(jwtgo.MapClaims).VerifyIssuer(i.tenant.issuer, false)
|
||||
if !checkIss {
|
||||
return nil, fmt.Errorf("invalid issuer")
|
||||
}
|
||||
|
||||
// Verify 'sub' claim
|
||||
if _, ok := token.Claims.(jwtgo.MapClaims)["sub"]; !ok {
|
||||
return nil, fmt.Errorf("sub claim is required")
|
||||
}
|
||||
|
||||
// find the key
|
||||
if _, ok := token.Header["kid"]; !ok {
|
||||
return nil, fmt.Errorf("kid not found")
|
||||
}
|
||||
|
||||
kid := token.Header["kid"].(string)
|
||||
|
||||
key, err := i.tenant.certs.Key(kid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no cert for kid found: %w", err)
|
||||
}
|
||||
|
||||
// find algorithm
|
||||
if _, ok := token.Header["alg"]; !ok {
|
||||
return nil, fmt.Errorf("kid not found")
|
||||
}
|
||||
|
||||
alg := token.Header["alg"].(string)
|
||||
|
||||
if key.Alg() != alg {
|
||||
return nil, fmt.Errorf("signing method doesn't match")
|
||||
}
|
||||
|
||||
// get the public key
|
||||
publicKey, err := key.PublicKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid public key: %w", err)
|
||||
}
|
||||
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
func (i *identity) VerifyServiceBasicAuth(password string) bool {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
|
||||
if !i.isValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
if !i.user.Auth.Services.Basic.Enable {
|
||||
return false
|
||||
}
|
||||
|
||||
return i.user.Auth.Services.Basic.Password == password
|
||||
}
|
||||
|
||||
func (i *identity) VerifyServiceToken(token string) bool {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
|
||||
if !i.isValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
return i.user.Auth.Services.Token == token
|
||||
}
|
||||
|
||||
func (i *identity) isValid() bool {
|
||||
return i.valid
|
||||
}
|
||||
|
||||
func (i *identity) IsSuperuser() bool {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
|
||||
return i.user.Superuser
|
||||
}
|
||||
|
||||
type IdentityVerifier interface {
|
||||
VerifyAPIPassword(password string) bool
|
||||
VerifyAPIAuth0(jwt string) bool
|
||||
|
||||
VerifyServiceBasicAuth(password string) bool
|
||||
VerifyServiceToken(token string) bool
|
||||
|
||||
IsSuperuser() bool
|
||||
}
|
||||
|
||||
type IdentityManager interface {
|
||||
Create(identity User) error
|
||||
Remove(name string) error
|
||||
Get(name string) (User, error)
|
||||
GetVerifier(name string) (IdentityVerifier, error)
|
||||
Rename(oldname, newname string) error
|
||||
Update(name string, identity User) error
|
||||
|
||||
Load(path string) error
|
||||
Save(path string) error
|
||||
}
|
||||
|
||||
type Auth0Tenant struct {
|
||||
Domain string
|
||||
Audience string
|
||||
ClientID string
|
||||
}
|
||||
|
||||
func (t *Auth0Tenant) key() string {
|
||||
return t.Domain + t.Audience
|
||||
}
|
||||
|
||||
type auth0Tenant struct {
|
||||
domain string
|
||||
issuer string
|
||||
audience string
|
||||
clientIDs []string
|
||||
certs jwks.JWKS
|
||||
}
|
||||
|
||||
func newAuth0Tenant(tenant Auth0Tenant) (*auth0Tenant, error) {
|
||||
t := &auth0Tenant{
|
||||
domain: tenant.Domain,
|
||||
issuer: "https://" + tenant.Domain + "/",
|
||||
audience: tenant.Audience,
|
||||
clientIDs: []string{tenant.ClientID},
|
||||
certs: nil,
|
||||
}
|
||||
|
||||
url := t.issuer + "/.well-known/jwks.json"
|
||||
certs, err := jwks.NewFromURL(url, jwks.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.certs = certs
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
type identityManager struct {
|
||||
identities map[string]*identity
|
||||
tenants map[string]*auth0Tenant
|
||||
|
||||
auth0UserIdentityMap map[string]string
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewIdentityManager() (IdentityManager, error) {
|
||||
return &identityManager{
|
||||
identities: map[string]*identity{},
|
||||
tenants: map[string]*auth0Tenant{},
|
||||
auth0UserIdentityMap: map[string]string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *identityManager) Create(u User) error {
|
||||
if err := u.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.lock.Lock()
|
||||
defer i.lock.Unlock()
|
||||
|
||||
_, ok := i.identities[u.Name]
|
||||
if ok {
|
||||
return fmt.Errorf("identity already exists")
|
||||
}
|
||||
|
||||
identity := u.marshalIdentity()
|
||||
|
||||
if identity.user.Auth.API.Auth0.Enable {
|
||||
if _, ok := i.auth0UserIdentityMap[identity.user.Auth.API.Auth0.User]; ok {
|
||||
return fmt.Errorf("the Auth0 user has already an identity")
|
||||
}
|
||||
|
||||
auth0Key := identity.user.Auth.API.Auth0.Tenant.key()
|
||||
|
||||
if _, ok := i.tenants[auth0Key]; !ok {
|
||||
tenant, err := newAuth0Tenant(identity.user.Auth.API.Auth0.Tenant)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.tenants[auth0Key] = tenant
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
i.identities[identity.user.Name] = identity
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *identityManager) Update(name string, identity User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *identityManager) Remove(name string) error {
|
||||
i.lock.Lock()
|
||||
defer i.lock.Unlock()
|
||||
|
||||
user, ok := i.identities[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(i.identities, name)
|
||||
|
||||
user.lock.Lock()
|
||||
user.valid = false
|
||||
user.lock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *identityManager) getIdentity(name string) (*identity, error) {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
|
||||
identity, ok := i.identities[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func (i *identityManager) Get(name string) (User, error) {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
|
||||
identity, ok := i.identities[name]
|
||||
if !ok {
|
||||
return User{}, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
return identity.user, nil
|
||||
}
|
||||
|
||||
func (i *identityManager) GetVerifier(name string) (IdentityVerifier, error) {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
|
||||
identity, ok := i.identities[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func (i *identityManager) Rename(oldname, newname string) error {
|
||||
i.lock.Lock()
|
||||
defer i.lock.Unlock()
|
||||
|
||||
identity, ok := i.identities[oldname]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := i.identities[newname]; ok {
|
||||
return fmt.Errorf("the new name already exists")
|
||||
}
|
||||
|
||||
delete(i.identities, oldname)
|
||||
|
||||
identity.user.Name = newname
|
||||
i.identities[newname] = identity
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *identityManager) Load(path string) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (i *identityManager) Save(path string) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
"github.com/datarhei/core/v16/session"
|
||||
|
||||
@ -195,6 +196,8 @@ type Config struct {
|
||||
// ListenAndServe, so it's not possible to modify the configuration
|
||||
// with methods like tls.Config.SetSessionTicketKeys.
|
||||
TLSConfig *tls.Config
|
||||
|
||||
IAM iam.IAM
|
||||
}
|
||||
|
||||
// Server represents a RTMP server
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/iam"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
"github.com/datarhei/core/v16/session"
|
||||
srt "github.com/datarhei/gosrt"
|
||||
@ -164,6 +165,8 @@ type Config struct {
|
||||
Collector session.Collector
|
||||
|
||||
SRTLogTopics []string
|
||||
|
||||
IAM iam.IAM
|
||||
}
|
||||
|
||||
// Server represents a SRT server
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user