diff --git a/app/api/api.go b/app/api/api.go index 09d09f9d..3e8b3bec 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -27,6 +27,8 @@ import ( httpfs "github.com/datarhei/core/v16/http/fs" "github.com/datarhei/core/v16/http/router" "github.com/datarhei/core/v16/iam" + iamaccess "github.com/datarhei/core/v16/iam/access" + iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/io/fs" "github.com/datarhei/core/v16/log" "github.com/datarhei/core/v16/math/rand" @@ -390,14 +392,14 @@ func (a *api) start() error { } { - superuser := iam.User{ + superuser := iamidentity.User{ Name: cfg.API.Auth.Username, Superuser: true, - Auth: iam.UserAuth{ - API: iam.UserAuthAPI{ - Auth0: iam.UserAuthAPIAuth0{}, + Auth: iamidentity.UserAuth{ + API: iamidentity.UserAuthAPI{ + Auth0: iamidentity.UserAuthAPIAuth0{}, }, - Services: iam.UserAuthServices{ + Services: iamidentity.UserAuthServices{ Token: []string{ cfg.RTMP.Token, cfg.SRT.Token, @@ -412,7 +414,7 @@ func (a *api) start() error { if cfg.API.Auth.Auth0.Enable { superuser.Auth.API.Auth0.User = cfg.API.Auth.Auth0.Tenants[0].Users[0] - superuser.Auth.API.Auth0.Tenant = iam.Auth0Tenant{ + superuser.Auth.API.Auth0.Tenant = iamidentity.Auth0Tenant{ Domain: cfg.API.Auth.Auth0.Tenants[0].Domain, Audience: cfg.API.Auth.Auth0.Tenants[0].Audience, ClientID: cfg.API.Auth.Auth0.Tenants[0].ClientID, @@ -431,12 +433,23 @@ func (a *api) start() error { secret = cfg.API.Auth.Username + cfg.API.Auth.Password + cfg.API.Auth.JWT.Secret } + policyAdapter, err := iamaccess.NewJSONAdapter(fs, "./policy.json", nil) + if err != nil { + return err + } + + identityAdapter, err := iamidentity.NewJSONAdapter(fs, "./users.json", nil) + if err != nil { + return err + } + manager, err := iam.NewIAM(iam.Config{ - FS: fs, - Superuser: superuser, - JWTRealm: "datarhei-core", - JWTSecret: secret, - Logger: a.log.logger.core.WithComponent("IAM"), + PolicyAdapter: policyAdapter, + IdentityAdapter: identityAdapter, + Superuser: superuser, + JWTRealm: "datarhei-core", + JWTSecret: secret, + Logger: a.log.logger.core.WithComponent("IAM"), }) if err != nil { return fmt.Errorf("iam: %w", err) @@ -445,7 +458,7 @@ func (a *api) start() error { // Check if there are already file created by IAM. If not, create policies // and users based on the config in order to mimic the behaviour before IAM. if len(fs.List("/", "/*.json")) == 0 { - policies := []iam.Policy{ + policies := []iamaccess.Policy{ { Name: "$anon", Domain: "$none", @@ -466,19 +479,19 @@ func (a *api) start() error { }, } - users := map[string]iam.User{} + users := map[string]iamidentity.User{} if cfg.Storage.Memory.Auth.Enable && cfg.Storage.Memory.Auth.Username != superuser.Name { - users[cfg.Storage.Memory.Auth.Username] = iam.User{ + users[cfg.Storage.Memory.Auth.Username] = iamidentity.User{ Name: cfg.Storage.Memory.Auth.Username, - Auth: iam.UserAuth{ - Services: iam.UserAuthServices{ + Auth: iamidentity.UserAuth{ + Services: iamidentity.UserAuthServices{ Basic: []string{cfg.Storage.Memory.Auth.Password}, }, }, } - policies = append(policies, iam.Policy{ + policies = append(policies, iamaccess.Policy{ Name: cfg.Storage.Memory.Auth.Username, Domain: "$none", Resource: "fs:/memfs/**", @@ -490,10 +503,10 @@ func (a *api) start() error { if s.Auth.Enable && s.Auth.Username != superuser.Name { user, ok := users[s.Auth.Username] if !ok { - users[s.Auth.Username] = iam.User{ + users[s.Auth.Username] = iamidentity.User{ Name: s.Auth.Username, - Auth: iam.UserAuth{ - Services: iam.UserAuthServices{ + Auth: iamidentity.UserAuth{ + Services: iamidentity.UserAuthServices{ Basic: []string{s.Auth.Password}, }, }, @@ -503,7 +516,7 @@ func (a *api) start() error { users[s.Auth.Username] = user } - policies = append(policies, iam.Policy{ + policies = append(policies, iamaccess.Policy{ Name: s.Auth.Username, Domain: "$none", Resource: "fs:" + s.Mountpoint + "/**", @@ -513,7 +526,7 @@ func (a *api) start() error { } if cfg.RTMP.Enable && len(cfg.RTMP.Token) == 0 { - policies = append(policies, iam.Policy{ + policies = append(policies, iamaccess.Policy{ Name: "$anon", Domain: "$none", Resource: "rtmp:/**", @@ -522,7 +535,7 @@ func (a *api) start() error { } if cfg.SRT.Enable && len(cfg.SRT.Token) == 0 { - policies = append(policies, iam.Policy{ + policies = append(policies, iamaccess.Policy{ Name: "$anon", Domain: "$none", Resource: "srt:**", @@ -737,7 +750,7 @@ func (a *api) start() error { } template += "/{name}" - var identity iam.IdentityVerifier = nil + var identity iamidentity.Verifier = nil if len(config.Owner) == 0 { identity = a.iam.GetDefaultVerifier() @@ -763,7 +776,7 @@ func (a *api) start() error { template += ",mode:publish" } - var identity iam.IdentityVerifier = nil + var identity iamidentity.Verifier = nil if len(config.Owner) == 0 { identity = a.iam.GetDefaultVerifier() diff --git a/http/api/iam.go b/http/api/iam.go index aea4f50b..69519e5c 100644 --- a/http/api/iam.go +++ b/http/api/iam.go @@ -1,6 +1,9 @@ package api -import "github.com/datarhei/core/v16/iam" +import ( + "github.com/datarhei/core/v16/iam/access" + "github.com/datarhei/core/v16/iam/identity" +) type IAMUser struct { Name string `json:"name"` @@ -9,7 +12,7 @@ type IAMUser struct { Policies []IAMPolicy `json:"policies"` } -func (u *IAMUser) Marshal(user iam.User, policies []iam.Policy) { +func (u *IAMUser) Marshal(user identity.User, policies []access.Policy) { u.Name = user.Name u.Superuser = user.Superuser u.Auth = IAMUserAuth{ @@ -39,33 +42,33 @@ func (u *IAMUser) Marshal(user iam.User, policies []iam.Policy) { } } -func (u *IAMUser) Unmarshal() (iam.User, []iam.Policy) { - iamuser := iam.User{ +func (u *IAMUser) Unmarshal() (identity.User, []access.Policy) { + iamuser := identity.User{ Name: u.Name, Superuser: u.Superuser, - Auth: iam.UserAuth{ - API: iam.UserAuthAPI{ + Auth: identity.UserAuth{ + API: identity.UserAuthAPI{ Password: u.Auth.API.Password, - Auth0: iam.UserAuthAPIAuth0{ + Auth0: identity.UserAuthAPIAuth0{ User: u.Auth.API.Auth0.User, - Tenant: iam.Auth0Tenant{ + Tenant: identity.Auth0Tenant{ Domain: u.Auth.API.Auth0.Tenant.Domain, Audience: u.Auth.API.Auth0.Tenant.Audience, ClientID: u.Auth.API.Auth0.Tenant.ClientID, }, }, }, - Services: iam.UserAuthServices{ + Services: identity.UserAuthServices{ Basic: u.Auth.Services.Basic, Token: u.Auth.Services.Token, }, }, } - iampolicies := []iam.Policy{} + iampolicies := []access.Policy{} for _, p := range u.Policies { - iampolicies = append(iampolicies, iam.Policy{ + iampolicies = append(iampolicies, access.Policy{ Name: u.Name, Domain: p.Domain, Resource: p.Resource, diff --git a/http/handler/api/iam.go b/http/handler/api/iam.go index 6382e73e..274f09c8 100644 --- a/http/handler/api/iam.go +++ b/http/handler/api/iam.go @@ -6,6 +6,7 @@ import ( "github.com/datarhei/core/v16/http/api" "github.com/datarhei/core/v16/http/handler/util" "github.com/datarhei/core/v16/iam" + "github.com/datarhei/core/v16/iam/identity" "github.com/labstack/echo/v4" ) @@ -70,11 +71,6 @@ func (h *IAMHandler) AddUser(c echo.Context) error { h.iam.AddPolicy(p.Name, p.Domain, p.Resource, p.Actions) } - err = h.iam.SaveIdentities() - if err != nil { - return api.Err(http.StatusInternalServerError, "Internal server error", "%s", err) - } - return c.JSON(http.StatusOK, user) } @@ -116,11 +112,6 @@ func (h *IAMHandler) RemoveUser(c echo.Context) error { return api.Err(http.StatusBadRequest, "Bad request", "%s", err) } - err = h.iam.SaveIdentities() - if err != nil { - return api.Err(http.StatusInternalServerError, "Internal server error", "%s", err) - } - // Remove all policies of that user h.iam.RemovePolicy(name, "", "", nil) @@ -153,7 +144,7 @@ func (h *IAMHandler) UpdateUser(c echo.Context) error { return api.Err(http.StatusForbidden, "Forbidden", "Not allowed to modify this user") } - var iamuser iam.User + var iamuser identity.User var err error if name != "$anon" { @@ -162,7 +153,7 @@ func (h *IAMHandler) UpdateUser(c echo.Context) error { return api.Err(http.StatusNotFound, "Not found", "%s", err) } } else { - iamuser = iam.User{ + iamuser = identity.User{ Name: "$anon", } } @@ -205,11 +196,6 @@ func (h *IAMHandler) UpdateUser(c echo.Context) error { h.iam.AddPolicy(p.Name, p.Domain, p.Resource, p.Actions) } - err = h.iam.SaveIdentities() - if err != nil { - return api.Err(http.StatusInternalServerError, "Internal server error", "%s", err) - } - return c.JSON(http.StatusOK, user) } @@ -239,7 +225,7 @@ func (h *IAMHandler) UpdateUserPolicies(c echo.Context) error { return api.Err(http.StatusForbidden, "Forbidden", "Not allowed to modify this user") } - var iamuser iam.User + var iamuser identity.User var err error if name != "$anon" { @@ -248,7 +234,7 @@ func (h *IAMHandler) UpdateUserPolicies(c echo.Context) error { return api.Err(http.StatusNotFound, "Not found", "%s", err) } } else { - iamuser = iam.User{ + iamuser = identity.User{ Name: "$anon", } } @@ -300,7 +286,7 @@ func (h *IAMHandler) GetUser(c echo.Context) error { return api.Err(http.StatusForbidden, "Forbidden", "Not allowed to access this user") } - var iamuser iam.User + var iamuser identity.User var err error if name != "$anon" { @@ -311,13 +297,13 @@ func (h *IAMHandler) GetUser(c echo.Context) error { if !superuser && name != iamuser.Name { if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { - iamuser = iam.User{ + iamuser = identity.User{ Name: iamuser.Name, } } } } else { - iamuser = iam.User{ + iamuser = identity.User{ Name: "$anon", } } diff --git a/http/handler/api/restream_test.go b/http/handler/api/restream_test.go index 6cebaff0..d64dc9bb 100644 --- a/http/handler/api/restream_test.go +++ b/http/handler/api/restream_test.go @@ -10,6 +10,8 @@ import ( "github.com/datarhei/core/v16/http/api" "github.com/datarhei/core/v16/http/mock" "github.com/datarhei/core/v16/iam" + "github.com/datarhei/core/v16/iam/access" + "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/io/fs" "github.com/labstack/echo/v4" @@ -33,9 +35,20 @@ func getDummyRestreamHandler() (*RestreamHandler, error) { return nil, fmt.Errorf("failed to create memory filesystem: %w", err) } + policyAdapter, err := access.NewJSONAdapter(memfs, "./policy.json", nil) + if err != nil { + return nil, err + } + + identityAdapter, err := identity.NewJSONAdapter(memfs, "./users.json", nil) + if err != nil { + return nil, err + } + iam, err := iam.NewIAM(iam.Config{ - FS: memfs, - Superuser: iam.User{ + PolicyAdapter: policyAdapter, + IdentityAdapter: identityAdapter, + Superuser: identity.User{ Name: "foobar", }, JWTRealm: "", diff --git a/http/middleware/iam/iam.go b/http/middleware/iam/iam.go index 8a1b8d75..c275753e 100644 --- a/http/middleware/iam/iam.go +++ b/http/middleware/iam/iam.go @@ -44,6 +44,7 @@ import ( "github.com/datarhei/core/v16/http/api" "github.com/datarhei/core/v16/http/handler/util" "github.com/datarhei/core/v16/iam" + iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/log" jwtgo "github.com/golang-jwt/jwt/v4" @@ -117,7 +118,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { isAPISuperuser = true } - var identity iam.IdentityVerifier = nil + var identity iamidentity.Verifier = nil var err error username := "$anon" @@ -231,7 +232,7 @@ var ErrAuthRequired = errors.New("unauthorized") var ErrUnauthorized = errors.New("unauthorized") var ErrBadRequest = errors.New("bad request") -func (m *iammiddleware) findIdentityFromBasicAuth(c echo.Context) (iam.IdentityVerifier, error) { +func (m *iammiddleware) findIdentityFromBasicAuth(c echo.Context) (iamidentity.Verifier, error) { basic := "basic" auth := c.Request().Header.Get(echo.HeaderAuthorization) l := len(basic) @@ -290,7 +291,7 @@ func (m *iammiddleware) findIdentityFromBasicAuth(c echo.Context) (iam.IdentityV return identity, nil } -func (m *iammiddleware) findIdentityFromJWT(c echo.Context) (iam.IdentityVerifier, error) { +func (m *iammiddleware) findIdentityFromJWT(c echo.Context) (iamidentity.Verifier, error) { // Look for an Auth header values := c.Request().Header.Values("Authorization") prefix := "Bearer " @@ -356,7 +357,7 @@ func (m *iammiddleware) findIdentityFromJWT(c echo.Context) (iam.IdentityVerifie return identity, nil } -func (m *iammiddleware) findIdentityFromUserpass(c echo.Context) (iam.IdentityVerifier, error) { +func (m *iammiddleware) findIdentityFromUserpass(c echo.Context) (iamidentity.Verifier, error) { var login api.Login if err := util.ShouldBindJSON(c, &login); err != nil { @@ -383,7 +384,7 @@ func (m *iammiddleware) findIdentityFromUserpass(c echo.Context) (iam.IdentityVe return identity, nil } -func (m *iammiddleware) findIdentityFromAuth0(c echo.Context) (iam.IdentityVerifier, error) { +func (m *iammiddleware) findIdentityFromAuth0(c echo.Context) (iamidentity.Verifier, error) { // Look for an Auth header values := c.Request().Header.Values("Authorization") prefix := "Bearer " diff --git a/http/middleware/iam/iam_test.go b/http/middleware/iam/iam_test.go index 096c3803..d82e730e 100644 --- a/http/middleware/iam/iam_test.go +++ b/http/middleware/iam/iam_test.go @@ -14,6 +14,8 @@ import ( apihandler "github.com/datarhei/core/v16/http/handler/api" "github.com/datarhei/core/v16/http/validator" "github.com/datarhei/core/v16/iam" + iamaccess "github.com/datarhei/core/v16/iam/access" + iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/io/fs" "github.com/labstack/echo/v4" @@ -28,9 +30,20 @@ func getIAM() (iam.IAM, error) { return nil, err } + policyAdapter, err := iamaccess.NewJSONAdapter(dummyfs, "./policy.json", nil) + if err != nil { + return nil, err + } + + identityAdapter, err := iamidentity.NewJSONAdapter(dummyfs, "./users.json", nil) + if err != nil { + return nil, err + } + i, err := iam.NewIAM(iam.Config{ - FS: dummyfs, - Superuser: iam.User{ + PolicyAdapter: policyAdapter, + IdentityAdapter: identityAdapter, + Superuser: iamidentity.User{ Name: "admin", }, JWTRealm: "datarhei-core", @@ -41,13 +54,13 @@ func getIAM() (iam.IAM, error) { return nil, err } - i.CreateIdentity(iam.User{ + i.CreateIdentity(iamidentity.User{ Name: "foobar", - Auth: iam.UserAuth{ - API: iam.UserAuthAPI{ + Auth: iamidentity.UserAuth{ + API: iamidentity.UserAuthAPI{ Password: "secret", }, - Services: iam.UserAuthServices{ + Services: iamidentity.UserAuthServices{ Basic: []string{"secret"}, }, }, diff --git a/http/mock/mock.go b/http/mock/mock.go index 0ea2d1cb..10f8bf2e 100644 --- a/http/mock/mock.go +++ b/http/mock/mock.go @@ -17,6 +17,8 @@ import ( "github.com/datarhei/core/v16/http/errorhandler" "github.com/datarhei/core/v16/http/validator" "github.com/datarhei/core/v16/iam" + iamaccess "github.com/datarhei/core/v16/iam/access" + iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/io/fs" "github.com/datarhei/core/v16/restream" @@ -53,9 +55,20 @@ func DummyRestreamer(pathPrefix string) (restream.Restreamer, error) { return nil, err } + policyAdapter, err := iamaccess.NewJSONAdapter(memfs, "./policy.json", nil) + if err != nil { + return nil, err + } + + identityAdapter, err := iamidentity.NewJSONAdapter(memfs, "./users.json", nil) + if err != nil { + return nil, err + } + iam, err := iam.NewIAM(iam.Config{ - FS: memfs, - Superuser: iam.User{ + PolicyAdapter: policyAdapter, + IdentityAdapter: identityAdapter, + Superuser: iamidentity.User{ Name: "foobar", }, JWTRealm: "", diff --git a/iam/access.go b/iam/access/access.go similarity index 78% rename from iam/access.go rename to iam/access/access.go index 9f754504..c876da5d 100644 --- a/iam/access.go +++ b/iam/access/access.go @@ -1,10 +1,8 @@ -package iam +package access import ( - "fmt" "strings" - "github.com/datarhei/core/v16/io/fs" "github.com/datarhei/core/v16/log" "github.com/casbin/casbin/v2" @@ -18,43 +16,40 @@ type Policy struct { Actions []string } -type AccessEnforcer interface { +type Enforcer interface { Enforce(name, domain, resource, action string) (bool, string) HasDomain(name string) bool ListDomains() []string } -type AccessManager interface { - AccessEnforcer +type Manager interface { + Enforcer HasPolicy(name, domain, resource string, actions []string) bool AddPolicy(name, domain, resource string, actions []string) bool RemovePolicy(name, domain, resource string, actions []string) bool ListPolicies(name, domain, resource string, actions []string) []Policy + ReloadPolicies() error } type access struct { - fs fs.Filesystem logger log.Logger - adapter *adapter + adapter Adapter + model model.Model enforcer *casbin.Enforcer } -type AccessConfig struct { - FS fs.Filesystem - Logger log.Logger +type Config struct { + Adapter Adapter + Logger log.Logger } -func NewAccessManager(config AccessConfig) (AccessManager, error) { +func New(config Config) (Manager, error) { am := &access{ - fs: config.FS, - logger: config.Logger, - } - - if am.fs == nil { - return nil, fmt.Errorf("a filesystem has to be provided") + adapter: config.Adapter, + logger: config.Logger, } if am.logger == nil { @@ -68,12 +63,7 @@ func NewAccessManager(config AccessConfig) (AccessManager, error) { m.AddDef("e", "e", "some(where (p.eft == allow))") m.AddDef("m", "m", `g(r.sub, p.sub, r.dom) && r.dom == p.dom && ResourceMatch(r.obj, r.dom, p.obj) && ActionMatch(r.act, p.act) || r.sub == "$superuser"`) - a, err := newAdapter(am.fs, "./policy.json", am.logger) - if err != nil { - return nil, err - } - - e, err := casbin.NewEnforcer(m, a) + e, err := casbin.NewEnforcer(m, am.adapter) if err != nil { return nil, err } @@ -82,7 +72,7 @@ func NewAccessManager(config AccessConfig) (AccessManager, error) { e.AddFunction("ActionMatch", actionMatchFunc) am.enforcer = e - am.adapter = a + am.model = m return am, nil } @@ -129,20 +119,18 @@ func (am *access) ListPolicies(name, domain, resource string, actions []string) return policies } +func (am *access) ReloadPolicies() error { + am.model.ClearPolicy() + + return am.adapter.LoadPolicy(am.model) +} + func (am *access) HasDomain(name string) bool { - groups := am.adapter.getAllDomains() - - for _, g := range groups { - if g == name { - return true - } - } - - return false + return am.adapter.HasDomain(name) } func (am *access) ListDomains() []string { - return am.adapter.getAllDomains() + return am.adapter.AllDomains() } func (am *access) Enforce(name, domain, resource, action string) (bool, string) { diff --git a/iam/access_test.go b/iam/access/access_test.go similarity index 88% rename from iam/access_test.go rename to iam/access/access_test.go index 01983091..47bb9d43 100644 --- a/iam/access_test.go +++ b/iam/access/access_test.go @@ -1,4 +1,4 @@ -package iam +package access import ( "testing" @@ -7,13 +7,22 @@ import ( "github.com/stretchr/testify/require" ) -func TestAccessManager(t *testing.T) { +func createAdapter() (Adapter, error) { memfs, err := fs.NewMemFilesystemFromDir("./fixtures", fs.MemConfig{}) + if err != nil { + return nil, err + } + + return NewJSONAdapter(memfs, "./policy.json", nil) +} + +func TestAccessManager(t *testing.T) { + adapter, err := createAdapter() require.NoError(t, err) - am, err := NewAccessManager(AccessConfig{ - FS: memfs, - Logger: nil, + am, err := New(Config{ + Adapter: adapter, + Logger: nil, }) require.NoError(t, err) diff --git a/iam/adapter.go b/iam/access/adapter.go similarity index 94% rename from iam/adapter.go rename to iam/access/adapter.go index 955ed4c2..16526a55 100644 --- a/iam/adapter.go +++ b/iam/access/adapter.go @@ -1,4 +1,4 @@ -package iam +package access import ( "encoding/json" @@ -12,6 +12,7 @@ import ( "github.com/datarhei/core/v16/log" "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" ) // Adapter is the file adapter for Casbin. @@ -24,7 +25,14 @@ type adapter struct { lock sync.Mutex } -func newAdapter(fs fs.Filesystem, filePath string, logger log.Logger) (*adapter, error) { +type Adapter interface { + persist.BatchAdapter + + AllDomains() []string + HasDomain(string) bool +} + +func NewJSONAdapter(fs fs.Filesystem, filePath string, logger log.Logger) (Adapter, error) { a := &adapter{ fs: fs, filePath: filePath, @@ -72,6 +80,8 @@ func (a *adapter) loadPolicyFile(model model.Model) error { return err } + model.ClearPolicy() + rule := [5]string{} for _, domain := range domains { rule[0] = "p" @@ -511,7 +521,10 @@ func (a *adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, return fmt.Errorf("not implemented") } -func (a *adapter) getAllDomains() []string { +func (a *adapter) AllDomains() []string { + a.lock.Lock() + defer a.lock.Unlock() + names := []string{} for _, domain := range a.domains { @@ -525,6 +538,23 @@ func (a *adapter) getAllDomains() []string { return names } +func (a *adapter) HasDomain(name string) bool { + a.lock.Lock() + defer a.lock.Unlock() + + for _, domain := range a.domains { + if domain.Name[0] == '$' { + continue + } + + if domain.Name == name { + return true + } + } + + return false +} + type Domain struct { Name string `json:"name"` Roles map[string][]Role `json:"roles"` diff --git a/iam/adapter_test.go b/iam/access/adapter_test.go similarity index 89% rename from iam/adapter_test.go rename to iam/access/adapter_test.go index 41c613e7..1a6dc287 100644 --- a/iam/adapter_test.go +++ b/iam/access/adapter_test.go @@ -1,4 +1,4 @@ -package iam +package access import ( "encoding/json" @@ -12,9 +12,12 @@ func TestAddPolicy(t *testing.T) { memfs, err := fs.NewMemFilesystem(fs.MemConfig{}) require.NoError(t, err) - a, err := newAdapter(memfs, "/policy.json", nil) + ai, err := NewJSONAdapter(memfs, "/policy.json", nil) require.NoError(t, err) + a, ok := ai.(*adapter) + require.True(t, ok) + err = a.AddPolicy("p", "p", []string{"foobar", "group", "resource", "action"}) require.NoError(t, err) @@ -53,9 +56,12 @@ func TestRemovePolicy(t *testing.T) { memfs, err := fs.NewMemFilesystem(fs.MemConfig{}) require.NoError(t, err) - a, err := newAdapter(memfs, "/policy.json", nil) + ai, err := NewJSONAdapter(memfs, "/policy.json", nil) require.NoError(t, err) + a, ok := ai.(*adapter) + require.True(t, ok) + err = a.AddPolicies("p", "p", [][]string{ {"foobar1", "group", "resource1", "action1"}, {"foobar2", "group", "resource2", "action2"}, diff --git a/iam/fixtures/policy.json b/iam/access/fixtures/policy.json similarity index 100% rename from iam/fixtures/policy.json rename to iam/access/fixtures/policy.json diff --git a/iam/functions.go b/iam/access/functions.go similarity index 99% rename from iam/functions.go rename to iam/access/functions.go index 41dce32d..8fef2633 100644 --- a/iam/functions.go +++ b/iam/access/functions.go @@ -1,4 +1,4 @@ -package iam +package access import ( "strings" diff --git a/iam/iam.go b/iam/iam.go index e611f05b..63362e60 100644 --- a/iam/iam.go +++ b/iam/iam.go @@ -1,7 +1,8 @@ package iam import ( - "github.com/datarhei/core/v16/io/fs" + "github.com/datarhei/core/v16/iam/access" + "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/log" ) @@ -18,21 +19,21 @@ type IAM interface { HasPolicy(name, domain, resource string, actions []string) bool AddPolicy(name, domain, resource string, actions []string) bool RemovePolicy(name, domain, resource string, actions []string) bool - - ListPolicies(name, domain, resource string, actions []string) []Policy + ListPolicies(name, domain, resource string, actions []string) []access.Policy + ReloadPolicies() error Validators() []string - CreateIdentity(u User) error - GetIdentity(name string) (User, error) - UpdateIdentity(name string, u User) error + CreateIdentity(u identity.User) error + GetIdentity(name string) (identity.User, error) + UpdateIdentity(name string, u identity.User) error DeleteIdentity(name string) error - ListIdentities() []User - SaveIdentities() error + ListIdentities() []identity.User + ReloadIndentities() error - GetVerifier(name string) (IdentityVerifier, error) - GetVerfierFromAuth0(name string) (IdentityVerifier, error) - GetDefaultVerifier() IdentityVerifier + GetVerifier(name string) (identity.Verifier, error) + GetVerfierFromAuth0(name string) (identity.Verifier, error) + GetDefaultVerifier() identity.Verifier CreateJWT(name string) (string, string, error) @@ -40,23 +41,24 @@ type IAM interface { } type iam struct { - im IdentityManager - am AccessManager + im identity.Manager + am access.Manager logger log.Logger } type Config struct { - FS fs.Filesystem - Superuser User - JWTRealm string - JWTSecret string - Logger log.Logger + PolicyAdapter access.Adapter + IdentityAdapter identity.Adapter + Superuser identity.User + JWTRealm string + JWTSecret string + Logger log.Logger } func NewIAM(config Config) (IAM, error) { - im, err := NewIdentityManager(IdentityConfig{ - FS: config.FS, + im, err := identity.New(identity.Config{ + Adapter: config.IdentityAdapter, Superuser: config.Superuser, JWTRealm: config.JWTRealm, JWTSecret: config.JWTSecret, @@ -66,9 +68,9 @@ func NewIAM(config Config) (IAM, error) { return nil, err } - am, err := NewAccessManager(AccessConfig{ - FS: config.FS, - Logger: config.Logger, + am, err := access.New(access.Config{ + Adapter: config.PolicyAdapter, + Logger: config.Logger, }) if err != nil { return nil, err @@ -138,15 +140,15 @@ func (i *iam) Enforce(name, domain, resource, action string) bool { return ok } -func (i *iam) CreateIdentity(u User) error { +func (i *iam) CreateIdentity(u identity.User) error { return i.im.Create(u) } -func (i *iam) GetIdentity(name string) (User, error) { +func (i *iam) GetIdentity(name string) (identity.User, error) { return i.im.Get(name) } -func (i *iam) UpdateIdentity(name string, u User) error { +func (i *iam) UpdateIdentity(name string, u identity.User) error { return i.im.Update(name, u) } @@ -154,23 +156,23 @@ func (i *iam) DeleteIdentity(name string) error { return i.im.Delete(name) } -func (i *iam) ListIdentities() []User { +func (i *iam) ListIdentities() []identity.User { return nil } -func (i *iam) SaveIdentities() error { - return i.im.Save() +func (i *iam) ReloadIndentities() error { + return i.im.Reload() } -func (i *iam) GetVerifier(name string) (IdentityVerifier, error) { +func (i *iam) GetVerifier(name string) (identity.Verifier, error) { return i.im.GetVerifier(name) } -func (i *iam) GetVerfierFromAuth0(name string) (IdentityVerifier, error) { +func (i *iam) GetVerfierFromAuth0(name string) (identity.Verifier, error) { return i.im.GetVerifierFromAuth0(name) } -func (i *iam) GetDefaultVerifier() IdentityVerifier { +func (i *iam) GetDefaultVerifier() identity.Verifier { v, _ := i.im.GetDefaultVerifier() return v @@ -220,6 +222,10 @@ func (i *iam) RemovePolicy(name, domain, resource string, actions []string) bool return i.am.RemovePolicy(name, domain, resource, actions) } -func (i *iam) ListPolicies(name, domain, resource string, actions []string) []Policy { +func (i *iam) ListPolicies(name, domain, resource string, actions []string) []access.Policy { return i.am.ListPolicies(name, domain, resource, actions) } + +func (i *iam) ReloadPolicies() error { + return i.am.ReloadPolicies() +} diff --git a/iam/identity/adapter.go b/iam/identity/adapter.go new file mode 100644 index 00000000..d288e31d --- /dev/null +++ b/iam/identity/adapter.go @@ -0,0 +1,89 @@ +package identity + +import ( + "encoding/json" + "fmt" + "os" + "sync" + + "github.com/datarhei/core/v16/io/fs" + "github.com/datarhei/core/v16/log" +) + +type Adapter interface { + LoadIdentities() ([]User, error) + SaveIdentities(user []User) error +} + +type fileAdapter struct { + fs fs.Filesystem + filePath string + logger log.Logger + lock sync.Mutex +} + +func NewJSONAdapter(fs fs.Filesystem, filePath string, logger log.Logger) (Adapter, error) { + a := &fileAdapter{ + fs: fs, + filePath: filePath, + logger: logger, + } + + if a.fs == nil { + return nil, fmt.Errorf("a filesystem must be provided") + } + + if a.filePath == "" { + return nil, fmt.Errorf("invalid file path, file path cannot be empty") + } + + if a.logger == nil { + a.logger = log.New("") + } + + return a, nil +} + +func (a *fileAdapter) LoadIdentities() ([]User, error) { + a.lock.Lock() + defer a.lock.Unlock() + + if _, err := a.fs.Stat(a.filePath); os.IsNotExist(err) { + return nil, nil + } + + data, err := a.fs.ReadFile(a.filePath) + if err != nil { + return nil, err + } + + users := []User{} + + err = json.Unmarshal(data, &users) + if err != nil { + return nil, err + } + + a.logger.Debug().WithField("path", a.filePath).Log("Identity file loaded") + + return users, nil +} + +func (a *fileAdapter) SaveIdentities(user []User) error { + a.lock.Lock() + defer a.lock.Unlock() + + jsondata, err := json.MarshalIndent(user, "", " ") + if err != nil { + return err + } + + _, _, err = a.fs.WriteFileSafe(a.filePath, jsondata) + if err != nil { + return err + } + + a.logger.Debug().WithField("path", a.filePath).Log("Identity file save") + + return nil +} diff --git a/iam/identity.go b/iam/identity/identity.go similarity index 89% rename from iam/identity.go rename to iam/identity/identity.go index c732517a..a25778ee 100644 --- a/iam/identity.go +++ b/iam/identity/identity.go @@ -1,15 +1,12 @@ -package iam +package identity import ( - "encoding/json" "fmt" - "os" "regexp" "sync" "time" "github.com/datarhei/core/v16/iam/jwks" - "github.com/datarhei/core/v16/io/fs" "github.com/datarhei/core/v16/log" "github.com/google/uuid" @@ -79,7 +76,7 @@ func (u *User) clone() User { return user } -type IdentityVerifier interface { +type Verifier interface { Name() string VerifyJWT(jwt string) (bool, error) @@ -369,21 +366,23 @@ func (i *identity) IsSuperuser() bool { return i.user.Superuser } -type IdentityManager interface { +type Manager interface { Create(identity User) error Update(name string, identity User) error Delete(name string) error Get(name string) (User, error) - GetVerifier(name string) (IdentityVerifier, error) - GetVerifierFromAuth0(name string) (IdentityVerifier, error) - GetDefaultVerifier() (IdentityVerifier, error) + GetVerifier(name string) (Verifier, error) + GetVerifierFromAuth0(name string) (Verifier, error) + GetDefaultVerifier() (Verifier, error) + + Reload() error // Reload users from adapter + Save() error // Save users to adapter + List() []User // List all users Validators() []string CreateJWT(name string) (string, string, error) - Save() error - Autosave(bool) Close() } @@ -395,8 +394,7 @@ type identityManager struct { auth0UserIdentityMap map[string]string - fs fs.Filesystem - filePath string + adapter Adapter autosave bool logger log.Logger @@ -406,21 +404,20 @@ type identityManager struct { lock sync.RWMutex } -type IdentityConfig struct { - FS fs.Filesystem +type Config struct { + Adapter Adapter Superuser User JWTRealm string JWTSecret string Logger log.Logger } -func NewIdentityManager(config IdentityConfig) (IdentityManager, error) { +func New(config Config) (Manager, error) { im := &identityManager{ identities: map[string]*identity{}, tenants: map[string]*auth0Tenant{}, auth0UserIdentityMap: map[string]string{}, - fs: config.FS, - filePath: "./users.json", + adapter: config.Adapter, jwtRealm: config.JWTRealm, jwtSecret: []byte(config.JWTSecret), logger: config.Logger, @@ -430,13 +427,8 @@ func NewIdentityManager(config IdentityConfig) (IdentityManager, error) { im.logger = log.New("") } - if im.fs == nil { - return nil, fmt.Errorf("no filesystem provided") - } - - err := im.load(im.filePath) - if err != nil { - return nil, err + if im.adapter == nil { + return nil, fmt.Errorf("no adapter provided") } config.Superuser.Superuser = true @@ -446,7 +438,13 @@ func NewIdentityManager(config IdentityConfig) (IdentityManager, error) { } im.root = identity - im.autosave = true + + err = im.Reload() + if err != nil { + return nil, err + } + + im.Save() return im, nil } @@ -455,7 +453,7 @@ func (im *identityManager) Close() { im.lock.Lock() defer im.lock.Unlock() - im.fs = nil + im.adapter = nil im.auth0UserIdentityMap = map[string]string{} im.identities = map[string]*identity{} im.root = nil @@ -467,6 +465,51 @@ func (im *identityManager) Close() { im.tenants = map[string]*auth0Tenant{} } +func (im *identityManager) Reload() error { + users, err := im.adapter.LoadIdentities() + if err != nil { + return fmt.Errorf("load users from adapter: %w", err) + } + + im.lock.Lock() + defer im.lock.Unlock() + + im.autosave = false + defer func() { + im.autosave = true + }() + + names := []string{} + + for name := range im.identities { + names = append(names, name) + } + + for _, name := range names { + im.delete(name) + } + + for _, u := range users { + if im.root != nil && u.Name == im.root.user.Name { + continue + } + + _, ok := im.identities[u.Name] + if ok { + continue + } + + identity, err := im.create(u) + if err != nil { + continue + } + + im.identities[identity.user.Name] = identity + } + + return nil +} + func (im *identityManager) Create(u User) error { if err := u.validate(); err != nil { return err @@ -492,7 +535,7 @@ func (im *identityManager) Create(u User) error { im.identities[identity.user.Name] = identity if im.autosave { - im.save(im.filePath) + im.save() } return nil @@ -580,7 +623,7 @@ func (im *identityManager) Update(name string, u User) error { }).Log("Identity updated") if im.autosave { - im.save(im.filePath) + im.save() } return nil @@ -590,7 +633,12 @@ func (im *identityManager) Delete(name string) error { im.lock.Lock() defer im.lock.Unlock() - return im.delete(name) + err := im.delete(name) + if err != nil { + return err + } + + return nil } func (im *identityManager) delete(name string) error { @@ -611,7 +659,7 @@ func (im *identityManager) delete(name string) error { if len(identity.user.Auth.API.Auth0.User) == 0 { if im.autosave { - im.save(im.filePath) + im.save() } return nil @@ -633,7 +681,7 @@ func (im *identityManager) delete(name string) error { delete(im.tenants, identity.user.Auth.API.Auth0.Tenant.key()) if im.autosave { - im.save(im.filePath) + im.save() } return nil @@ -657,7 +705,9 @@ func (im *identityManager) delete(name string) error { } if im.autosave { - im.save(im.filePath) + if err := im.save(); err != nil { + return err + } } return nil @@ -696,14 +746,14 @@ func (im *identityManager) Get(name string) (User, error) { return user, nil } -func (im *identityManager) GetVerifier(name string) (IdentityVerifier, error) { +func (im *identityManager) GetVerifier(name string) (Verifier, error) { im.lock.RLock() defer im.lock.RUnlock() return im.getIdentity(name) } -func (im *identityManager) GetVerifierFromAuth0(name string) (IdentityVerifier, error) { +func (im *identityManager) GetVerifierFromAuth0(name string) (Verifier, error) { im.lock.RLock() defer im.lock.RUnlock() @@ -715,65 +765,38 @@ func (im *identityManager) GetVerifierFromAuth0(name string) (IdentityVerifier, return im.getIdentity(name) } -func (im *identityManager) GetDefaultVerifier() (IdentityVerifier, error) { +func (im *identityManager) GetDefaultVerifier() (Verifier, error) { return im.root, nil } -func (im *identityManager) load(filePath string) error { - if _, err := im.fs.Stat(filePath); os.IsNotExist(err) { - return nil - } - - data, err := im.fs.ReadFile(filePath) - if err != nil { - return err - } +func (im *identityManager) List() []User { + im.lock.RLock() + defer im.lock.RUnlock() users := []User{} - err = json.Unmarshal(data, &users) - if err != nil { - return err + for _, identity := range im.identities { + users = append(users, identity.user.clone()) } - for _, u := range users { - err = im.Create(u) - if err != nil { - return err - } - } - - return nil + return users } func (im *identityManager) Save() error { im.lock.RLock() defer im.lock.RUnlock() - return im.save(im.filePath) + return im.save() } -func (im *identityManager) save(filePath string) error { - if filePath == "" { - return fmt.Errorf("invalid file path, file path cannot be empty") - } - +func (im *identityManager) save() error { users := []User{} for _, u := range im.identities { users = append(users, u.user) } - jsondata, err := json.MarshalIndent(users, "", " ") - if err != nil { - return err - } - - _, _, err = im.fs.WriteFileSafe(filePath, jsondata) - - im.logger.Debug().WithField("path", filePath).Log("Identity file save") - - return err + return im.adapter.SaveIdentities(users) } func (im *identityManager) Autosave(auto bool) { diff --git a/iam/identity_test.go b/iam/identity/identity_test.go similarity index 92% rename from iam/identity_test.go rename to iam/identity/identity_test.go index 93c6d0cf..87c3e724 100644 --- a/iam/identity_test.go +++ b/iam/identity/identity_test.go @@ -1,4 +1,4 @@ -package iam +package identity import ( "testing" @@ -7,6 +7,15 @@ import ( "github.com/stretchr/testify/require" ) +func createAdapter() (Adapter, error) { + dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + if err != nil { + return nil, err + } + + return NewJSONAdapter(dummyfs, "./users.json", nil) +} + func TestUserName(t *testing.T) { user := User{} @@ -39,11 +48,11 @@ func TestIdentity(t *testing.T) { identity.user.Superuser = true require.True(t, identity.IsSuperuser()) - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adapter, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err := New(Config{ + Adapter: adapter, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -58,11 +67,11 @@ func TestIdentity(t *testing.T) { } func TestDefaultIdentity(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adapter, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err := New(Config{ + Adapter: adapter, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -186,11 +195,11 @@ func TestIdentityServiceTokenAuth(t *testing.T) { } func TestJWT(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adapter, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err := New(Config{ + Adapter: adapter, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -239,11 +248,11 @@ func TestJWT(t *testing.T) { } func TestCreateUser(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adapter, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err := New(Config{ + Adapter: adapter, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -263,11 +272,11 @@ func TestCreateUser(t *testing.T) { } func TestCreateUserAuth0(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adapter, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err := New(Config{ + Adapter: adapter, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -383,11 +392,13 @@ func TestCreateUserAuth0(t *testing.T) { } func TestLoadAndSave(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adptr, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + dummyfs := adptr.(*fileAdapter).fs + + im, err := New(Config{ + Adapter: adptr, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -416,8 +427,8 @@ func TestLoadAndSave(t *testing.T) { err = im.Save() require.NoError(t, err) - im, err = NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err = New(Config{ + Adapter: adptr, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -432,11 +443,11 @@ func TestLoadAndSave(t *testing.T) { } func TestUpdateUser(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adapter, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err := New(Config{ + Adapter: adapter, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -480,11 +491,11 @@ func TestUpdateUser(t *testing.T) { } func TestUpdateUserAuth0(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adapter, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err := New(Config{ + Adapter: adapter, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -537,11 +548,11 @@ func TestUpdateUserAuth0(t *testing.T) { } func TestRemoveUser(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adapter, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err := New(Config{ + Adapter: adapter, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -633,11 +644,11 @@ func TestRemoveUser(t *testing.T) { } func TestRemoveUserAuth0(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adapter, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + im, err := New(Config{ + Adapter: adapter, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -716,11 +727,13 @@ func TestRemoveUserAuth0(t *testing.T) { } func TestAutosave(t *testing.T) { - dummyfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + adptr, err := createAdapter() require.NoError(t, err) - im, err := NewIdentityManager(IdentityConfig{ - FS: dummyfs, + dummyfs := adptr.(*fileAdapter).fs + + im, err := New(Config{ + Adapter: adptr, Superuser: User{Name: "foobar"}, JWTRealm: "test-realm", JWTSecret: "abc123", @@ -739,8 +752,6 @@ func TestAutosave(t *testing.T) { require.NoError(t, err) require.Equal(t, []byte("[]"), data) - im.Autosave(true) - err = im.Create(User{Name: "foobaz"}) require.NoError(t, err) diff --git a/restream/restream_test.go b/restream/restream_test.go index 6344ca81..50168942 100644 --- a/restream/restream_test.go +++ b/restream/restream_test.go @@ -7,6 +7,8 @@ import ( "github.com/datarhei/core/v16/ffmpeg" "github.com/datarhei/core/v16/iam" + iamaccess "github.com/datarhei/core/v16/iam/access" + iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/io/fs" "github.com/datarhei/core/v16/net" @@ -39,9 +41,20 @@ func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmp return nil, err } + policyAdapter, err := iamaccess.NewJSONAdapter(dummyfs, "./policy.json", nil) + if err != nil { + return nil, err + } + + identityAdapter, err := iamidentity.NewJSONAdapter(dummyfs, "./users.json", nil) + if err != nil { + return nil, err + } + iam, err := iam.NewIAM(iam.Config{ - FS: dummyfs, - Superuser: iam.User{ + PolicyAdapter: policyAdapter, + IdentityAdapter: identityAdapter, + Superuser: iamidentity.User{ Name: "foobar", }, JWTRealm: "", diff --git a/restream/rewrite/rewrite.go b/restream/rewrite/rewrite.go index 1112ce23..fd598c3d 100644 --- a/restream/rewrite/rewrite.go +++ b/restream/rewrite/rewrite.go @@ -5,7 +5,7 @@ import ( "fmt" "net/url" - "github.com/datarhei/core/v16/iam" + iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/rtmp" srturl "github.com/datarhei/core/v16/srt/url" ) @@ -25,7 +25,7 @@ type Config struct { // to a new identity, i.e. adjusting the credentials to the given identity. type Rewriter interface { - RewriteAddress(address string, identity iam.IdentityVerifier, mode Access) string + RewriteAddress(address string, identity iamidentity.Verifier, mode Access) string } type rewrite struct { @@ -44,7 +44,7 @@ func New(config Config) (Rewriter, error) { return r, nil } -func (g *rewrite) RewriteAddress(address string, identity iam.IdentityVerifier, mode Access) string { +func (g *rewrite) RewriteAddress(address string, identity iamidentity.Verifier, mode Access) string { u, err := url.Parse(address) if err != nil { return address @@ -104,7 +104,7 @@ func (g *rewrite) isLocal(u *url.URL) bool { return host == base.Host } -func (g *rewrite) httpURL(u *url.URL, mode Access, identity iam.IdentityVerifier) string { +func (g *rewrite) httpURL(u *url.URL, mode Access, identity iamidentity.Verifier) string { password := identity.GetServiceBasicAuth() if len(password) == 0 { @@ -116,7 +116,7 @@ func (g *rewrite) httpURL(u *url.URL, mode Access, identity iam.IdentityVerifier return u.String() } -func (g *rewrite) rtmpURL(u *url.URL, mode Access, identity iam.IdentityVerifier) string { +func (g *rewrite) rtmpURL(u *url.URL, mode Access, identity iamidentity.Verifier) string { token := identity.GetServiceToken() // Remove the existing token from the path @@ -131,7 +131,7 @@ func (g *rewrite) rtmpURL(u *url.URL, mode Access, identity iam.IdentityVerifier return u.String() } -func (g *rewrite) srtURL(u *url.URL, mode Access, identity iam.IdentityVerifier) string { +func (g *rewrite) srtURL(u *url.URL, mode Access, identity iamidentity.Verifier) string { token := identity.GetServiceToken() q := u.Query() diff --git a/restream/rewrite/rewrite_test.go b/restream/rewrite/rewrite_test.go index f1a1ae1b..92c2c504 100644 --- a/restream/rewrite/rewrite_test.go +++ b/restream/rewrite/rewrite_test.go @@ -4,21 +4,26 @@ import ( "net/url" "testing" - "github.com/datarhei/core/v16/iam" + iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/io/fs" "github.com/stretchr/testify/require" ) -func getIdentityManager(enableBasic bool) iam.IdentityManager { +func getIdentityManager(enableBasic bool) (iamidentity.Manager, error) { dummyfs, _ := fs.NewMemFilesystem(fs.MemConfig{}) - superuser := iam.User{ + adapter, err := iamidentity.NewJSONAdapter(dummyfs, "./users.json", nil) + if err != nil { + return nil, err + } + + superuser := iamidentity.User{ Name: "foobar", Superuser: false, - Auth: iam.UserAuth{ - API: iam.UserAuthAPI{}, - Services: iam.UserAuthServices{ + Auth: iamidentity.UserAuth{ + API: iamidentity.UserAuthAPI{}, + Services: iamidentity.UserAuthServices{ Token: []string{"servicetoken"}, }, }, @@ -28,19 +33,20 @@ func getIdentityManager(enableBasic bool) iam.IdentityManager { superuser.Auth.Services.Basic = []string{"basicauthpassword"} } - im, _ := iam.NewIdentityManager(iam.IdentityConfig{ - FS: dummyfs, + im, err := iamidentity.New(iamidentity.Config{ + Adapter: adapter, Superuser: superuser, JWTRealm: "", JWTSecret: "", Logger: nil, }) - return im + return im, err } func TestRewriteHTTP(t *testing.T) { - im := getIdentityManager(false) + im, err := getIdentityManager(false) + require.NoError(t, err) rewrite, err := New(Config{ HTTPBase: "http://localhost:8080/", @@ -70,7 +76,8 @@ func TestRewriteHTTP(t *testing.T) { } func TestRewriteHTTPPassword(t *testing.T) { - im := getIdentityManager(true) + im, err := getIdentityManager(true) + require.NoError(t, err) rewrite, err := New(Config{ HTTPBase: "http://localhost:8080/", @@ -100,7 +107,8 @@ func TestRewriteHTTPPassword(t *testing.T) { } func TestRewriteRTMP(t *testing.T) { - im := getIdentityManager(false) + im, err := getIdentityManager(false) + require.NoError(t, err) rewrite, err := New(Config{ RTMPBase: "rtmp://localhost:1935/live", @@ -128,7 +136,8 @@ func TestRewriteRTMP(t *testing.T) { } func TestRewriteSRT(t *testing.T) { - im := getIdentityManager(false) + im, err := getIdentityManager(false) + require.NoError(t, err) rewrite, err := New(Config{ SRTBase: "srt://localhost:6000/", diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 38dd276d..5b6f7952 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -13,6 +13,7 @@ import ( "github.com/datarhei/core/v16/cluster/proxy" "github.com/datarhei/core/v16/iam" + iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/log" "github.com/datarhei/core/v16/session" @@ -467,7 +468,7 @@ func (s *server) findIdentityFromStreamKey(key string) (string, error) { return "$anon", nil } - var identity iam.IdentityVerifier + var identity iamidentity.Verifier var err error var token string diff --git a/srt/srt.go b/srt/srt.go index 77dbd5e9..2476c37f 100644 --- a/srt/srt.go +++ b/srt/srt.go @@ -11,6 +11,7 @@ import ( "github.com/datarhei/core/v16/cluster/proxy" "github.com/datarhei/core/v16/iam" + iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/log" "github.com/datarhei/core/v16/session" "github.com/datarhei/core/v16/srt/url" @@ -477,7 +478,7 @@ func (s *server) findIdentityFromToken(key string) (string, error) { return "$anon", nil } - var identity iam.IdentityVerifier + var identity iamidentity.Verifier var err error var token string