Skip to content

Commit 54a6bba

Browse files
committed
Added initial config for RBAC support
1 parent e61bd26 commit 54a6bba

File tree

4 files changed

+163
-1
lines changed

4 files changed

+163
-1
lines changed

internal/metadata/metadata.go

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
_ "modernc.org/sqlite"
2222
)
2323

24-
const CURRENT_DB_VERSION = 5
24+
const CURRENT_DB_VERSION = 6
2525

2626
// Metadata is the metadata persistence layer
2727
type Metadata struct {
@@ -213,6 +213,17 @@ func (m *Metadata) VersionUpgrade(config *types.ServerConfig) error {
213213
}
214214
}
215215

216+
if version < 6 {
217+
m.Info().Msg("Upgrading to version 6")
218+
if _, err := tx.ExecContext(ctx, `create table config(version_id text, user_id text, update_time `+system.MapDataType(m.dbType, "datetime")+", config json)"); err != nil {
219+
return err
220+
}
221+
222+
if _, err := tx.ExecContext(ctx, `update version set version=6, last_upgraded=`+system.FuncNow(m.dbType)); err != nil {
223+
return err
224+
}
225+
}
226+
216227
if err := tx.Commit(); err != nil {
217228
return err
218229
}
@@ -627,6 +638,88 @@ func (m *Metadata) UpdateSyncStatus(ctx context.Context, tx types.Transaction, i
627638
return nil
628639
}
629640

641+
var ErrConfigAlreadyExists = errors.New("config already exists")
642+
var ErrConfigNotFound = errors.New("config not found")
643+
644+
func (m *Metadata) InitConfig(ctx context.Context, user string, dynamicConfig *types.DynamicConfig) error {
645+
configJson, err := json.Marshal(dynamicConfig)
646+
if err != nil {
647+
return fmt.Errorf("error marshalling dynamic config: %w", err)
648+
}
649+
650+
tx, err := m.db.BeginTx(ctx, nil)
651+
if err != nil {
652+
return fmt.Errorf("error beginning transaction: %w", err)
653+
}
654+
655+
defer tx.Rollback()
656+
countResult := tx.QueryRowContext(ctx, system.RebindQuery(m.dbType, `select count(*) from config`))
657+
var rowCount int
658+
err = countResult.Scan(&rowCount)
659+
if err != nil {
660+
return fmt.Errorf("error scanning config: %w", err)
661+
}
662+
if rowCount > 0 {
663+
return ErrConfigAlreadyExists
664+
}
665+
666+
_, err = tx.ExecContext(ctx, system.RebindQuery(m.dbType,
667+
`insert into config values (?, ?, `+system.FuncNow(m.dbType)+", ?)"),
668+
dynamicConfig.VersionId, user, string(configJson))
669+
if err != nil {
670+
return fmt.Errorf("error inserting config: %w", err)
671+
}
672+
err = tx.Commit()
673+
if err != nil {
674+
return fmt.Errorf("error committing transaction: %w", err)
675+
}
676+
return nil
677+
}
678+
679+
func (m *Metadata) UpdateConfig(ctx context.Context, user string, oldVersionId string, dynamicConfig *types.DynamicConfig) error {
680+
configJson, err := json.Marshal(dynamicConfig)
681+
if err != nil {
682+
return fmt.Errorf("error marshalling dynamic config: %w", err)
683+
}
684+
685+
result, err := m.db.ExecContext(ctx, system.RebindQuery(m.dbType,
686+
`update config set version_id = ?, config = ?, update_time = `+system.FuncNow(m.dbType)+", user_id = ? where version_id = ?"),
687+
dynamicConfig.VersionId, string(configJson), user, oldVersionId)
688+
if err != nil {
689+
return fmt.Errorf("error updating config: %w", err)
690+
}
691+
rowsAffected, err := result.RowsAffected()
692+
if err != nil {
693+
return fmt.Errorf("error getting rows affected: %w", err)
694+
}
695+
if rowsAffected == 0 {
696+
return fmt.Errorf("no config entry found with version id for update: %s", oldVersionId)
697+
}
698+
return nil
699+
}
700+
701+
func (m *Metadata) GetConfig() (*types.DynamicConfig, error) {
702+
var configStr sql.NullString
703+
row := m.db.QueryRow(system.RebindQuery(m.dbType, `select config from config`))
704+
err := row.Scan(&configStr)
705+
if err != nil {
706+
if err == sql.ErrNoRows {
707+
return nil, ErrConfigNotFound
708+
}
709+
return nil, fmt.Errorf("error querying config: %w", err)
710+
}
711+
712+
var config types.DynamicConfig
713+
if configStr.Valid && configStr.String != "" {
714+
err = json.Unmarshal([]byte(configStr.String), &config)
715+
if err != nil {
716+
return nil, fmt.Errorf("error unmarshalling config: %w", err)
717+
}
718+
}
719+
720+
return &config, nil
721+
}
722+
630723
// BeginTransaction starts a new Transaction
631724
func (m *Metadata) BeginTransaction(ctx context.Context) (types.Transaction, error) {
632725
tx, err := m.db.BeginTx(ctx, nil)

internal/server/router.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,11 @@ func (h *Handler) listSyncEntries(r *http.Request) (any, error) {
10781078
return results, nil
10791079
}
10801080

1081+
func (h *Handler) configUpdate(r *http.Request) (any, error) {
1082+
// TODO
1083+
return nil, nil
1084+
}
1085+
10811086
// serveInternal returns a handler for the internal APIs for app admin and management
10821087
func (h *Handler) serveInternal(enableBasicAuth bool) http.Handler {
10831088
// These API's are mounted at /_openrun
@@ -1203,6 +1208,11 @@ func (h *Handler) serveInternal(enableBasicAuth bool) http.Handler {
12031208
h.apiHandler(w, r, enableBasicAuth, "list_sync", h.listSyncEntries)
12041209
}))
12051210

1211+
// API to run sync
1212+
r.Post("/config", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1213+
h.apiHandler(w, r, enableBasicAuth, "config_update", h.configUpdate)
1214+
}))
1215+
12061216
return r
12071217
}
12081218

internal/server/server.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ type Server struct {
136136
auditDB *sql.DB
137137
auditDbType system.DBType
138138
syncTimer *time.Ticker
139+
configMu sync.RWMutex
140+
dynamicConfig *types.DynamicConfig
139141
}
140142

141143
// NewServer creates a new instance of the OpenRun Server
@@ -203,12 +205,43 @@ func NewServer(config *types.ServerConfig) (*Server, error) {
203205

204206
initOpenRunPlugin(server)
205207

208+
server.dynamicConfig, err = server.db.GetConfig()
209+
if err != nil && !errors.Is(err, metadata.ErrConfigNotFound) {
210+
return nil, fmt.Errorf("error getting dynamic config: %w", err)
211+
}
212+
213+
if server.dynamicConfig == nil || server.dynamicConfig.VersionId == "" {
214+
// Initialize dynamic config if not already done
215+
if server.dynamicConfig == nil {
216+
server.dynamicConfig = &types.DynamicConfig{}
217+
}
218+
server.dynamicConfig.VersionId = "ver_" + ksuid.New().String()
219+
err = server.db.InitConfig(context.Background(), "admin", server.dynamicConfig)
220+
if err != nil {
221+
if !errors.Is(err, metadata.ErrConfigAlreadyExists) {
222+
return nil, fmt.Errorf("error init dynamic config: %w", err)
223+
} else {
224+
server.dynamicConfig, err = server.db.GetConfig()
225+
if err != nil {
226+
return nil, fmt.Errorf("error getting dynamic config: %w", err)
227+
}
228+
}
229+
}
230+
// TODO : notify other instances about new dynamic config
231+
}
232+
206233
// Start the idle shutdown check
207234
server.syncTimer = time.NewTicker(time.Minute) // run sync every minute
208235
go server.syncRunner()
209236
return server, nil
210237
}
211238

239+
func (s *Server) GetRBACConfig() types.RBACConfig {
240+
s.configMu.RLock()
241+
defer s.configMu.RUnlock()
242+
return s.dynamicConfig.RBAC
243+
}
244+
212245
func (s *Server) appNotifyFunction(updatePayload types.AppUpdatePayload) {
213246
if updatePayload.ServerId == types.CurrentServerId {
214247
s.Trace().Str("server_id", string(updatePayload.ServerId)).Msg("Ignoring app update notification from self")

internal/types/types.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,3 +678,29 @@ type JSLibrary struct {
678678
EsbuildArgs [10]string // use an array so that the struct can be used as key in the jsCache map
679679
SanitizedFileName string
680680
}
681+
682+
// DynamicConfig is the configuration which is settable through API and is persisted to metadata
683+
type DynamicConfig struct {
684+
VersionId string `json:"version_id"`
685+
RBAC RBACConfig `json:"rbac"`
686+
}
687+
688+
type RBACConfig struct {
689+
Groups map[string][]string `json:"groups"` // groups names to user ids. These groups are appended to the groups info from SAML
690+
Roles map[string][]RBACPermission `json:"roles"` // roles names to permissions.
691+
Grants []RBACGrant `json:"grants"` // grants are used to grant permissions to users/groups for specific apps
692+
}
693+
694+
type RBACGrant struct {
695+
Description string `json:"description"`
696+
Users []string `json:"users"` // users/groups granted by this rule
697+
Roles []string `json:"roles"` // the roles granted by this rule
698+
Targets []string `json:"targets"` // the app path globs for which this grant applies
699+
}
700+
701+
type RBACPermission string
702+
703+
const (
704+
PermissionList RBACPermission = "list" // list apps
705+
PermissionAccess RBACPermission = "access" // access apps
706+
)

0 commit comments

Comments
 (0)