Skip to content

Commit

Permalink
Added permission model for API keys #79, replaced button text with icons
Browse files Browse the repository at this point in the history
  • Loading branch information
Forceu committed Nov 28, 2023
1 parent b0e44d2 commit c68afac
Show file tree
Hide file tree
Showing 24 changed files with 599 additions and 58 deletions.
4 changes: 2 additions & 2 deletions build/go-generate/updateVersionNumbers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (
"strings"
)

const versionJsAdmin = "1"
const versionJsAdmin = "2"
const versionJsDropzone = "3"
const versionJsE2EAdmin = "2"
const versionCssMain = "1"
const versionCssMain = "2"

const fileMain = "../../cmd/gokapi/Main.go"
const fileVersionConstants = "../../internal/webserver/web/templates/string_constants.tmpl"
Expand Down
2 changes: 1 addition & 1 deletion cmd/gokapi/Main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (

// versionGokapi is the current version in readable form.
// Other version numbers can be modified in /build/go-generate/updateVersionNumbers.go
const versionGokapi = "1.8.0beta1"
const versionGokapi = "1.8.0beta2"

// The following calls update the version numbers, update documentation, minify Js/CSS and build the WASM modules
//go:generate go run "../../build/go-generate/updateVersionNumbers.go"
Expand Down
19 changes: 17 additions & 2 deletions internal/configuration/configupgrade/Upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"github.com/forceu/gokapi/internal/configuration/database"
"github.com/forceu/gokapi/internal/configuration/database/legacydb"
"github.com/forceu/gokapi/internal/environment"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"os"
)

// CurrentConfigVersion is the version of the configuration structure. Used for upgrading
const CurrentConfigVersion = 15
const CurrentConfigVersion = 16

// DoUpgrade checks if an old version is present and updates it to the current version if required
func DoUpgrade(settings *models.Configuration, env *environment.Environment) bool {
Expand All @@ -36,13 +37,27 @@ func updateConfig(settings *models.Configuration, env *environment.Environment)
if settings.ConfigVersion < 14 {
settings.PublicName = "Gokapi"
}
// < v1.8.0
// < v1.8.0beta1
if settings.ConfigVersion < 15 {
fmt.Println("Migrating to SQLite...")
migrateToSqlite(env)
fmt.Println("Migration complete. You will need to login again.")
fmt.Println("It should be safe to delete the folder " + env.LegacyDbPath)
}
// < v1.8.0beta2
if settings.ConfigVersion < 16 {
exists, err := database.ColumnExists("ApiKeys", "Permissions")
helper.Check(err)
if !exists {
err = database.RawSqlite("ALTER TABLE ApiKeys ADD Permissions INTEGER NOT NULL DEFAULT 0;")
helper.Check(err)
}
apikeys := database.GetAllApiKeys()
for _, apikey := range apikeys {
apikey.Permissions = models.ApiPermAllNoApiMod
database.SaveApiKey(apikey)
}
}
}

// migrateToSqlite copies the content of the old bitcask database to a new sqlite database
Expand Down
39 changes: 38 additions & 1 deletion internal/configuration/database/Database.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,50 @@ func RunGarbageCollection() {
cleanUploadStatus()
}

// RawSqlite runs a raw SQL statement. Should only be used for upgrading
func RawSqlite(statement string) error {
_, err := sqliteDb.Exec(statement)
return err
}

type schemaPragma struct {
Cid string
Name string
Type string
NotNull int
DefaultVal sql.NullString
Pk int
}

// ColumnExists returns true if a column with the name columnName exists in table tableName
// Should only be used for upgrading
func ColumnExists(tableName, columnName string) (bool, error) {
rows, err := sqliteDb.Query("PRAGMA table_info(" + tableName + ")")
if err != nil {
return false, err
}
defer rows.Close()
for rows.Next() {
var pragmaInfo schemaPragma
err = rows.Scan(&pragmaInfo.Cid, &pragmaInfo.Name, &pragmaInfo.Type, &pragmaInfo.NotNull, &pragmaInfo.DefaultVal, &pragmaInfo.Pk)
if err != nil {
return false, err
}
if pragmaInfo.Name == columnName {
return true, nil
}
}
return false, nil
}

func createNewDatabase() {
sqlStmt := `
CREATE TABLE "ApiKeys" (
"Id" TEXT NOT NULL UNIQUE,
"FriendlyName" TEXT NOT NULL,
"LastUsed" INTEGER NOT NULL,
"LastUsedString" TEXT NOT NULL,
"Permissions" INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY("Id")
) WITHOUT ROWID;
CREATE TABLE "E2EConfig" (
Expand Down Expand Up @@ -114,6 +151,6 @@ func createNewDatabase() {
PRIMARY KEY("ChunkId")
) WITHOUT ROWID;
`
_, err := sqliteDb.Exec(sqlStmt)
err := RawSqlite(sqlStmt)
helper.Check(err)
}
9 changes: 6 additions & 3 deletions internal/configuration/database/Database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,25 @@ func TestApiKey(t *testing.T) {
SaveApiKey(models.ApiKey{
Id: "newkey",
FriendlyName: "New Key",
LastUsed: 100,
LastUsedString: "LastUsed",
LastUsed: 100,
Permissions: 20,
})
SaveApiKey(models.ApiKey{
Id: "newkey2",
FriendlyName: "New Key2",
LastUsed: 200,
LastUsedString: "LastUsed2",
LastUsed: 200,
Permissions: 40,
})

keys := GetAllApiKeys()
test.IsEqualInt(t, len(keys), 2)
test.IsEqualString(t, keys["newkey"].FriendlyName, "New Key")
test.IsEqualString(t, keys["newkey"].Id, "newkey")
test.IsEqualString(t, keys["newkey"].LastUsedString, "LastUsed")
test.IsEqualBool(t, keys["newkey"].LastUsed == 100, true)
test.IsEqualInt64(t, keys["newkey"].LastUsed, 100)
test.IsEqualInt(t, keys["newkey"].Permissions, 20)

Check failure on line 103 in internal/configuration/database/Database_test.go

View workflow job for this annotation

GitHub Actions / build

cannot use keys["newkey"].Permissions (value of type uint8) as int value in argument to test.IsEqualInt

test.IsEqualInt(t, len(GetAllApiKeys()), 2)
DeleteApiKey("newkey2")
Expand Down
11 changes: 7 additions & 4 deletions internal/configuration/database/apikeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type schemaApiKeys struct {
FriendlyName string
LastUsed int64
LastUsedString string
Permissions int
}

// GetAllApiKeys returns a map with all API keys
Expand All @@ -23,13 +24,14 @@ func GetAllApiKeys() map[string]models.ApiKey {
defer rows.Close()
for rows.Next() {
rowData := schemaApiKeys{}
err = rows.Scan(&rowData.Id, &rowData.FriendlyName, &rowData.LastUsed, &rowData.LastUsedString)
err = rows.Scan(&rowData.Id, &rowData.FriendlyName, &rowData.LastUsed, &rowData.LastUsedString, &rowData.Permissions)
helper.Check(err)
result[rowData.Id] = models.ApiKey{
Id: rowData.Id,
FriendlyName: rowData.FriendlyName,
LastUsed: rowData.LastUsed,
LastUsedString: rowData.LastUsedString,
Permissions: uint8(rowData.Permissions),
}
}
return result
Expand All @@ -39,7 +41,7 @@ func GetAllApiKeys() map[string]models.ApiKey {
func GetApiKey(id string) (models.ApiKey, bool) {
var rowResult schemaApiKeys
row := sqliteDb.QueryRow("SELECT * FROM ApiKeys WHERE Id = ?", id)
err := row.Scan(&rowResult.Id, &rowResult.FriendlyName, &rowResult.LastUsed, &rowResult.LastUsedString)
err := row.Scan(&rowResult.Id, &rowResult.FriendlyName, &rowResult.LastUsed, &rowResult.LastUsedString, &rowResult.Permissions)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return models.ApiKey{}, false
Expand All @@ -53,15 +55,16 @@ func GetApiKey(id string) (models.ApiKey, bool) {
FriendlyName: rowResult.FriendlyName,
LastUsed: rowResult.LastUsed,
LastUsedString: rowResult.LastUsedString,
Permissions: uint8(rowResult.Permissions),
}

return result, true
}

// SaveApiKey saves the API key to the database
func SaveApiKey(apikey models.ApiKey) {
_, err := sqliteDb.Exec("INSERT OR REPLACE INTO ApiKeys (Id, FriendlyName, LastUsed, LastUsedString) VALUES (?, ?, ?, ?)",
apikey.Id, apikey.FriendlyName, apikey.LastUsed, apikey.LastUsedString)
_, err := sqliteDb.Exec("INSERT OR REPLACE INTO ApiKeys (Id, FriendlyName, LastUsed, LastUsedString, Permissions) VALUES (?, ?, ?, ?, ?)",
apikey.Id, apikey.FriendlyName, apikey.LastUsed, apikey.LastUsedString, apikey.Permissions)
helper.Check(err)
}

Expand Down
44 changes: 43 additions & 1 deletion internal/models/Api.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,51 @@
package models

const (
ApiPermView = 1 << iota // upper case
ApiPermUpload // lower case
ApiPermDelete // capitalizes
ApiPermApiMod // reverses
)

const ApiPermNone = 0
const ApiPermAllNoApiMod = 7
const ApiPermAll = 15

// ApiKey contains data of a single api key
type ApiKey struct {
Id string `json:"Id"`
FriendlyName string `json:"FriendlyName"`
LastUsed int64 `json:"LastUsed"`
LastUsedString string `json:"LastUsedString"`
LastUsed int64 `json:"LastUsed"`
Permissions uint8 `json:"Permissions"`
}

func (key *ApiKey) SetPermission(permission uint8) {
key.Permissions |= permission
}
func (key *ApiKey) RemovePermission(permission uint8) {
key.Permissions &^= permission
}

func (key *ApiKey) HasPermission(permission uint8) bool {
if permission == ApiPermNone {
return true
}
return (key.Permissions & permission) == permission
}

func (key *ApiKey) HasPermissionView() bool {
return key.HasPermission(ApiPermView)
}

func (key *ApiKey) HasPermissionUpload() bool {
return key.HasPermission(ApiPermUpload)
}

func (key *ApiKey) HasPermissionDelete() bool {
return key.HasPermission(ApiPermDelete)
}

func (key *ApiKey) HasPermissionApiMod() bool {
return key.HasPermission(ApiPermApiMod)
}
Loading

0 comments on commit c68afac

Please sign in to comment.