From 1958eb16fcb65c0466bfdadf6484330c71cc2d14 Mon Sep 17 00:00:00 2001 From: Tom POUDEROUX Date: Wed, 21 May 2025 21:12:23 +0200 Subject: [PATCH] POC restricted access --- src/api/api.go | 129 +++++++++++++++++++++++++++++++++++++-- src/database/database.go | 123 +++++++++++++++++++++---------------- 2 files changed, 197 insertions(+), 55 deletions(-) diff --git a/src/api/api.go b/src/api/api.go index daacee4..0a56faf 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -16,6 +16,14 @@ const ( profiler_API_PROCESS_REQUEST string = "API Process Request" ) +type apiUserDefinition struct { + Id string + Name string + Permission string +} + +type apiHandleAdminFunction func(http.ResponseWriter, *http.Request, httprouter.Params, apiUserDefinition) + var apiRootPath string = "/api/v1" func Init(router *httprouter.Router) { @@ -34,7 +42,7 @@ func Init(router *httprouter.Router) { router.GET(apiRootPath+"/page/:folder/:page/content", apiHandler(apiPageFolderPageContent)) router.GET(apiRootPath+"/user", apiHandler(apiUser)) - router.POST(apiRootPath+"/user/:name", apiHandler(apiUser_Create)) + router.POST(apiRootPath+"/user/:name", apiHandler(apiAdminRestricted(apiUser_Create))) router.GET(apiRootPath+"/user/:name", apiHandler(apiUserName)) router.GET(apiRootPath+"/tag", apiHandler(apiTag)) @@ -58,6 +66,80 @@ func apiHandler(h httprouter.Handle) httprouter.Handle { } } +func apiAdminRestricted(h apiHandleAdminFunction) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + var auth string = r.Header.Get("Authorization") + if auth == "" { + writeErrorJSON(w, "", http.StatusForbidden) + return + } + + const lenBearer int = len("Bearer ") + if auth[:lenBearer] != "Bearer " { + writeErrorJSON(w, "", http.StatusForbidden) + return + } + var token string = auth[lenBearer:] + + var result database.DatabaseResult = database.ExecuteStoredRoutine("CheckAuthToken", token) + database.DecodeDatabaseResult(&result) + if result.ParsedResultLength == 0 { + writeErrorJSON(w, "", http.StatusForbidden) + return + } + + var user apiUserDefinition = apiUserDefinition{ + Id: result.ParsedResult["userId"][0].String, + Name: result.ParsedResult["userName"][0].String, + Permission: result.ParsedResult["userPermission"][0].String, + } + + if user.Permission != "ADMIN" { + writeErrorJSON(w, "", http.StatusForbidden) + return + } + + h(w, r, ps, user) + } +} + +func apiModeratorRestricted(h apiHandleAdminFunction) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + var auth string = r.Header.Get("Authorization") + if auth == "" { + writeErrorJSON(w, "", http.StatusForbidden) + return + } + + const lenBearer int = len("Bearer ") + if auth[:lenBearer] != "Bearer " { + writeErrorJSON(w, "", http.StatusForbidden) + return + } + var token string = auth[lenBearer:] + + var result database.DatabaseResult = database.ExecuteStoredRoutine("CheckAuthToken", token) + database.DecodeDatabaseResult(&result) + if result.ParsedResultLength == 0 { + writeErrorJSON(w, "", http.StatusForbidden) + return + } + + var user apiUserDefinition = apiUserDefinition{ + Id: result.ParsedResult["userId"][0].String, + Name: result.ParsedResult["userName"][0].String, + Permission: result.ParsedResult["userPermission"][0].String, + } + + if (user.Permission != "ADMIN") && (user.Permission != "MODERATOR") { + writeErrorJSON(w, "", http.StatusForbidden) + return + } + + h(w, r, ps, user) + } +} + // API ROOT func apiRoot(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -68,11 +150,20 @@ func apiRoot(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // API Auth func apiAuth(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "API Auth") + var ok bool + var id, name, permission string + ok, id, name, permission = verifyToken(r) + + if !ok { + writeErrorJSON(w, "Authentification required", http.StatusForbidden) + return + } + + fmt.Fprintf(w, "{\"User\": {\"Id\": %s, \"Name\": \"%s\", \"Permission\": \"%s\"}}", id, name, permission) } func apiAuthLogin(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "API Auth Login") + fmt.Fprintf(w, "API Auth Login\n") r.ParseForm() var username string = r.FormValue("username") var password string = r.FormValue("password") @@ -118,7 +209,7 @@ func apiUser(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Println(result) } -func apiUser_Create(w http.ResponseWriter, r *http.Request, params httprouter.Params) { +func apiUser_Create(w http.ResponseWriter, r *http.Request, params httprouter.Params, _ apiUserDefinition) { r.ParseForm() var username string = params.ByName("name") var password string = r.FormValue("password") @@ -174,6 +265,9 @@ func apiDebugDatabaseReset(w http.ResponseWriter, r *http.Request, _ httprouter. fmt.Fprintf(w, "Init Database...\n") w.(http.Flusher).Flush() database.UpdateDatabaseStructure() + fmt.Fprintf(w, "Setup default conf...\n") + database.ExecuteStoredRoutine("CreateUser", "admin", "admin") + database.ExecuteStoredRoutine("UpdateUser", "1", nil, "ADMIN", "NULL") fmt.Fprintf(w, "Done") } @@ -205,3 +299,30 @@ func setHeaderStream(w http.ResponseWriter) { w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") } + +func verifyToken(r *http.Request) (bool, string, string, string) { + var auth string = r.Header.Get("Authorization") + if auth == "" { + return false, "", "", "" + } + + const lenBearer int = len("Bearer ") + if auth[:lenBearer] != "Bearer " { + return false, "", "", "" + } + var token string = auth[lenBearer:] + + var result database.DatabaseResult = database.ExecuteStoredRoutine("CheckAuthToken", token) + database.DecodeDatabaseResult(&result) + if result.ParsedResultLength == 0 { + return false, "", "", "" + } + + var id, name, permission string = result.ParsedResult["userId"][0].String, result.ParsedResult["userName"][0].String, result.ParsedResult["userPermission"][0].String + + return true, id, name, permission +} + +func writeErrorJSON(w http.ResponseWriter, message string, codeRet int) { + http.Error(w, fmt.Sprintf("{\"Error\": {\"Code\": %d, \"Message\": \"%s\"}}", codeRet, message), codeRet) +} diff --git a/src/database/database.go b/src/database/database.go index d9e62b5..da9e644 100644 --- a/src/database/database.go +++ b/src/database/database.go @@ -32,10 +32,11 @@ func (e *DatabaseError) HasError() bool { } type DatabaseResult struct { - Error DatabaseError - SqlRows *sql.Rows - SqlResult sql.Result - ParsedResult map[string][]sql.NullString + Error DatabaseError + SqlRows *sql.Rows + SqlResult sql.Result + ParsedResultLength int64 + ParsedResult map[string][]sql.NullString } const ( @@ -256,6 +257,16 @@ var databaseStructure databaseDefinition = databaseDefinition{ Name: "user_id", Type: "int(11)", }, + { + Name: "creation", + Type: "DATE", + Default: "CURRENT_TIMESTAMP()", + }, + { + Name: "valid_until", + Type: "DATE", + Default: "NOW()", + }, }, }, { @@ -281,51 +292,6 @@ var databaseStructure databaseDefinition = databaseDefinition{ }, }, Indexes: []indexDefinition{ - // PRIMARY KEYS - /* - { - Name: "", - Type: database_ENUM_INDEX_PRIMARY, - Table: "user", - Columns: []string{ - "id", - }, - }, - { - Name: "", - Type: database_ENUM_INDEX_PRIMARY, - Table: "folder", - Columns: []string{ - "id", - }, - }, - { - Name: "", - Type: database_ENUM_INDEX_PRIMARY, - Table: "tag", - Columns: []string{ - "id", - }, - }, - { - Name: "", - Type: database_ENUM_INDEX_PRIMARY, - Table: "website", - Columns: []string{ - "id", - }, - }, - { - Name: "", - Type: database_ENUM_INDEX_PRIMARY, - Table: "tags_websites", - Columns: []string{ - "tag_id", - "website_id", - }, - }, - */ - // UNIQUE KEYS { @@ -380,6 +346,58 @@ var databaseStructure databaseDefinition = databaseDefinition{ Name: "ListUsers", Command: "SELECT id, name, permission_level FROM user;", }, + { + Name: "GetUserCount", + Command: "SELECT COUNT(user.id) as userCount FROM user;", + }, + { + Name: "GetWebsiteCount", + Command: "SELECT COUNT(website.id) as websiteCount FROM website;", + }, + { + Name: "CheckAuthToken", + Parameters: []storedRoutineParameterDefinition{ + { + Name: "f_token", + Type: "VARCHAR(256)", + Charset: "utf8mb4", + Collation: "utf8mb4_bin", + }, + }, + Command: "SELECT user.id as userId, user.name as userName, user.permission_level as userPermission FROM loginToken INNER JOIN user ON user.id = loginToken.user_id WHERE f_token = loginToken.id LIMIT 1;", + }, + { + Name: "UpdateUser", + Parameters: []storedRoutineParameterDefinition{ + { + Name: "f_user_id", + Type: "int(11)", + IsNullable: true, + }, + { + Name: "f_new_name", + Type: "VARCHAR(256)", + Charset: "utf8mb4", + Collation: "utf8mb4_unicode_ci", + IsNullable: true, + }, + { + Name: "f_new_permission_level", + Type: "VARCHAR(32)", + Charset: "utf8mb4", + Collation: "utf8mb4_unicode_ci", + IsNullable: true, + }, + { + Name: "f_new_email", + Type: "VARCHAR(256)", + Charset: "utf8mb4", + Collation: "utf8mb4_unicode_ci", + IsNullable: true, + }, + }, + Command: "UPDATE user SET name = IFNULL(f_new_name, name), permission_level = IFNULL(f_new_permission_level, permission_level), email = IFNULL(f_new_email, email);", + }, }, StoredFunctions: []storedFunctionDefinition{ { @@ -484,7 +502,7 @@ func connect() { var err error log.Printf("Connecting to database %s at %s:%d as %s\n", dbConfig.Database, dbConfig.Host, dbConfig.Port, dbConfig.User) - tStart := time.Now() + var tStart time.Time = time.Now() for i := range dbConfig.RetriesOnError { db, err = sql.Open("mysql", dbPath) if err == nil { @@ -568,6 +586,7 @@ func DecodeDatabaseResult(databaseResult *DatabaseResult) { } databaseResult.ParsedResult = map[string][]sql.NullString{} + databaseResult.ParsedResultLength = 0 var numCol int = len(columns) for _, c := range columns { databaseResult.ParsedResult[c] = make([]sql.NullString, 0) @@ -586,6 +605,7 @@ func DecodeDatabaseResult(databaseResult *DatabaseResult) { for i, c := range columns { databaseResult.ParsedResult[c] = append(databaseResult.ParsedResult[c], strArr[i]) } + databaseResult.ParsedResultLength++ } if err = rows.Err(); err != nil { @@ -1024,7 +1044,7 @@ func UpdateDatabaseStructure() { if i != 0 { command += ", " } - command += fmt.Sprintf("IN '%s' %s", pp.Name, pp.Type) + command += fmt.Sprintf("IN `%s` %s", pp.Name, pp.Type) if ppc, ok := pp.Charset.(string); ok { command += fmt.Sprintf(" CHARACTER SET %s", ppc) @@ -1038,6 +1058,7 @@ func UpdateDatabaseStructure() { fmt.Println(command) executeExec(command) } + initDatabaseStructure() } func DestroyDatabase() {