Skip to content

Commit

Permalink
Automatically add user when successfully authenticated with headers /…
Browse files Browse the repository at this point in the history
… oauth, disallow modifing own user permissions
  • Loading branch information
Forceu committed Dec 26, 2024
1 parent fa6e000 commit 96dee6f
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 56 deletions.
7 changes: 3 additions & 4 deletions internal/webserver/Webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,10 +355,9 @@ func showLogin(w http.ResponseWriter, r *http.Request) {
pw := r.Form.Get("password")
failedLogin := false
if pw != "" && user != "" {
if authentication.IsCorrectUsernameAndPassword(user, pw) {
isOauth := configuration.Get().Authentication.Method == authentication.OAuth2
interval := configuration.Get().Authentication.OAuthRecheckInterval
sessionmanager.CreateSession(w, isOauth, interval)
retrievedUser, validCredentials := authentication.IsCorrectUsernameAndPassword(user, pw)
if validCredentials {
sessionmanager.CreateSession(w, false, 0, retrievedUser.Id)
redirect(w, "admin")
return
}
Expand Down
62 changes: 37 additions & 25 deletions internal/webserver/api/Api.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ func Process(w http.ResponseWriter, r *http.Request, maxMemory int) {
case "/user/create":
addUser(w, request)
case "/user/changeRank":
changeUserRank(w, request)
changeUserRank(w, request, user)
case "/user/modify":
modifyUserPermission(w, request)
modifyUserPermission(w, request, user)
case "/user/delete":
deleteUser(w, request, user)
default:
Expand Down Expand Up @@ -570,15 +570,19 @@ func outputFileInfo(w http.ResponseWriter, file models.File) {
_, _ = w.Write(result)
}

func modifyUserPermission(w http.ResponseWriter, request apiRequest) {
user, ok := isValidUserForEditing(w, request)
func modifyUserPermission(w http.ResponseWriter, request apiRequest, user models.User) {
userEdit, ok := isValidUserForEditing(w, request)
if !ok {
return
}
if user.UserLevel == models.UserLevelSuperAdmin {
if userEdit.UserLevel == models.UserLevelSuperAdmin {
sendError(w, http.StatusBadRequest, "Cannot modify super admin")
return
}
if user.Id == userEdit.Id {
sendError(w, http.StatusBadRequest, "Cannot modify yourself")
return
}
reqPermission := request.usermodInfo.permission
addPerm := request.usermodInfo.grantPermission
validPermissions := []uint16{models.UserPermReplaceUploads,
Expand All @@ -592,44 +596,48 @@ func modifyUserPermission(w http.ResponseWriter, request apiRequest) {
}

if addPerm {
if !user.HasPermission(reqPermission) {
user.SetPermission(reqPermission)
database.SaveUser(user, false)
updateApiKeyPermsOnUserPermChange(user.Id, reqPermission, true)
if !userEdit.HasPermission(reqPermission) {
userEdit.SetPermission(reqPermission)
database.SaveUser(userEdit, false)
updateApiKeyPermsOnUserPermChange(userEdit.Id, reqPermission, true)
}
return
}
if user.HasPermission(reqPermission) {
user.RemovePermission(reqPermission)
database.SaveUser(user, false)
updateApiKeyPermsOnUserPermChange(user.Id, reqPermission, false)
if userEdit.HasPermission(reqPermission) {
userEdit.RemovePermission(reqPermission)
database.SaveUser(userEdit, false)
updateApiKeyPermsOnUserPermChange(userEdit.Id, reqPermission, false)
}
}

func changeUserRank(w http.ResponseWriter, request apiRequest) {
user, ok := isValidUserForEditing(w, request)
func changeUserRank(w http.ResponseWriter, request apiRequest, user models.User) {
userEdit, ok := isValidUserForEditing(w, request)
if !ok {
return
}
if user.UserLevel == models.UserLevelSuperAdmin {
if user.Id == userEdit.Id {
sendError(w, http.StatusBadRequest, "Cannot modify yourself")
return
}
if userEdit.UserLevel == models.UserLevelSuperAdmin {
sendError(w, http.StatusBadRequest, "Cannot modify super admin")
return
}
switch request.usermodInfo.newRank {
case "ADMIN":
user.UserLevel = models.UserLevelAdmin
user.Permissions = models.UserPermissionAll
updateApiKeyPermsOnUserPermChange(user.Id, models.UserPermReplaceUploads, true)
updateApiKeyPermsOnUserPermChange(user.Id, models.UserPermManageUsers, true)
userEdit.UserLevel = models.UserLevelAdmin
userEdit.Permissions = models.UserPermissionAll
updateApiKeyPermsOnUserPermChange(userEdit.Id, models.UserPermReplaceUploads, true)
updateApiKeyPermsOnUserPermChange(userEdit.Id, models.UserPermManageUsers, true)
case "USER":
user.UserLevel = models.UserLevelUser
user.Permissions = models.UserPermissionNone
updateApiKeyPermsOnUserPermChange(user.Id, models.UserPermReplaceUploads, false)
updateApiKeyPermsOnUserPermChange(user.Id, models.UserPermManageUsers, false)
userEdit.UserLevel = models.UserLevelUser
userEdit.Permissions = models.UserPermissionNone
updateApiKeyPermsOnUserPermChange(userEdit.Id, models.UserPermReplaceUploads, false)
updateApiKeyPermsOnUserPermChange(userEdit.Id, models.UserPermManageUsers, false)
default:
sendError(w, http.StatusBadRequest, "invalid rank sent")
}
database.SaveUser(user, false)
database.SaveUser(userEdit, false)
}

func updateApiKeyPermsOnUserPermChange(userId int, userPerm uint16, isNewlyGranted bool) {
Expand Down Expand Up @@ -667,6 +675,10 @@ func deleteUser(w http.ResponseWriter, request apiRequest, user models.User) {
sendError(w, http.StatusBadRequest, "Cannot delete super admin")
return
}
if user.Id == userToDelete.Id {
sendError(w, http.StatusBadRequest, "Cannot delete yourself")
return
}
database.DeleteUser(userToDelete.Id)
for _, file := range database.GetAllMetadata() {
if file.UserId == userToDelete.Id {
Expand Down
63 changes: 49 additions & 14 deletions internal/webserver/authentication/Authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"github.com/forceu/gokapi/internal/configuration"
"github.com/forceu/gokapi/internal/configuration/database"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/webserver/authentication/sessionmanager"
Expand Down Expand Up @@ -100,28 +101,35 @@ func IsAuthenticated(w http.ResponseWriter, r *http.Request) (bool, int) {
return true, userId
}
case Header:
if isGrantedHeader(r) {
return true, 0 // TODO
} // TODO
userId, ok := isGrantedHeader(r)
if ok {
return true, userId
}
case Disabled:
return true, 0
}
return false, -1
}

// isGrantedHeader returns true if the user was authenticated by a proxy header if enabled
func isGrantedHeader(r *http.Request) bool {
func isGrantedHeader(r *http.Request) (int, bool) {
if authSettings.HeaderKey == "" {
return false
return -1, false
}
value := r.Header.Get(authSettings.HeaderKey)
if value == "" {
return false
userName := r.Header.Get(authSettings.HeaderKey)
if userName == "" {

return -1, false
}
if len(authSettings.HeaderUsers) == 0 {
return true
user := getOrCreateUser(userName, userName)
return user.Id, true
}
if isUserInArray(userName, authSettings.HeaderUsers) {
user := getOrCreateUser(userName, userName)
return user.Id, true
}
return isUserInArray(value, authSettings.HeaderUsers)
return -1, false
}

func isUserInArray(userEntered string, allowedUsers []string) bool {
Expand Down Expand Up @@ -264,14 +272,35 @@ func CheckOauthUserAndRedirect(userInfo OAuthUserInfo, w http.ResponseWriter) er
}
}
if isValidOauthUser(userInfo, username, groups) {
sessionmanager.CreateSession(w, authSettings.Method == OAuth2, authSettings.OAuthRecheckInterval)
if userInfo.Email == "" {
userInfo.Email = username
}
user := getOrCreateUser(username, userInfo.Email)
sessionmanager.CreateSession(w, true, authSettings.OAuthRecheckInterval, user.Id)
redirect(w, "admin")
return nil
}
redirect(w, "error-auth")
return nil
}

func getOrCreateUser(username, email string) models.User {
user, ok := database.GetUserByEmail(email)
if !ok {
user = models.User{
Name: username,
Email: username,
UserLevel: models.UserLevelUser,
}
database.SaveUser(user, true)
user, ok = database.GetUserByEmail(email)
if !ok {
panic("unable to read new user")
}
}
return user
}

func isValidOauthUser(userInfo OAuthUserInfo, username string, groups []string) bool {
if userInfo.Subject == "" {
return false
Expand All @@ -293,9 +322,15 @@ func isGrantedSession(w http.ResponseWriter, r *http.Request) (int, bool) {
}

// IsCorrectUsernameAndPassword checks if a provided username and password is correct
func IsCorrectUsernameAndPassword(username, password string) bool {
return IsEqualStringConstantTime(username, authSettings.Username) &&
IsEqualStringConstantTime(configuration.HashPasswordCustomSalt(password, authSettings.SaltAdmin), authSettings.Password)
func IsCorrectUsernameAndPassword(userEmail, password string) (models.User, bool) {
user, ok := database.GetUserByEmail(userEmail)
if !ok {
return models.User{}, false
}
if IsEqualStringConstantTime(configuration.HashPasswordCustomSalt(password, authSettings.SaltAdmin), user.Password) {
return user, true
}
return models.User{}, false
}

// IsEqualStringConstantTime uses ConstantTimeCompare to prevent timing attack.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func useSession(w http.ResponseWriter, id string, session models.Session, isOaut
return false
}
if session.RenewAt < time.Now().Unix() {
CreateSession(w, isOauth, OAuthRecheckInterval)
CreateSession(w, isOauth, OAuthRecheckInterval, session.UserId)
database.DeleteSession(id)
}
go database.UpdateUserLastOnline(session.UserId)
Expand All @@ -51,7 +51,7 @@ func useSession(w http.ResponseWriter, id string, session models.Session, isOaut

// CreateSession creates a new session - called after login with correct username / password
// If sessions parameter is nil, it will be loaded from config
func CreateSession(w http.ResponseWriter, isOauth bool, OAuthRecheckInterval int) {
func CreateSession(w http.ResponseWriter, isOauth bool, OAuthRecheckInterval int, userId int) {
timeExpiry := time.Now().Add(cookieLifeAdmin)
if isOauth {
timeExpiry = time.Now().Add(time.Duration(OAuthRecheckInterval) * time.Hour)
Expand All @@ -61,6 +61,7 @@ func CreateSession(w http.ResponseWriter, isOauth bool, OAuthRecheckInterval int
database.SaveSession(sessionString, models.Session{
RenewAt: time.Now().Add(12 * time.Hour).Unix(),
ValidUntil: timeExpiry.Unix(),
UserId: userId,
})
writeSessionCookie(w, sessionString, timeExpiry)
}
Expand Down
22 changes: 11 additions & 11 deletions internal/webserver/web/templates/html_users.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -41,33 +41,33 @@
<td scope="col">{{ .UploadCount }}</td>
<td scope="col" class="prevent-select">

<i id="perm_replace_{{ .User.Id }}" class="bi bi-recycle {{if not .User.HasPermissionReplace}}perm-notgranted{{else}}perm-granted{{end}} {{if eq .User.UserLevel 0}}perm-nochange{{end}}" title="Replace own uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_REPLACE", "perm_replace_{{ .User.Id }}");'></i>
<i id="perm_replace_{{ .User.Id }}" class="bi bi-recycle {{if not .User.HasPermissionReplace}}perm-notgranted{{else}}perm-granted{{end}} {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}perm-nochange{{end}}" title="Replace own uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_REPLACE", "perm_replace_{{ .User.Id }}");'></i>

<i id="perm_list_{{ .User.Id }}" class="bi bi-eye {{if not .User.HasPermissionListOtherUploads}}perm-notgranted{{else}}perm-granted{{end}} {{if eq .User.UserLevel 0}}perm-nochange{{end}}" title="List other uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_LIST", "perm_list_{{ .User.Id }}");'></i>
<i id="perm_list_{{ .User.Id }}" class="bi bi-eye {{if not .User.HasPermissionListOtherUploads}}perm-notgranted{{else}}perm-granted{{end}} {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}perm-nochange{{end}}" title="List other uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_LIST", "perm_list_{{ .User.Id }}");'></i>

<i id="perm_edit_{{ .User.Id }}" class="bi bi-pencil {{if not .User.HasPermissionEditOtherUploads}}perm-notgranted{{else}}perm-granted{{end}} {{if eq .User.UserLevel 0}}perm-nochange{{end}}" title="Edit other uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_EDIT", "perm_edit_{{ .User.Id }}");'></i>
<i id="perm_edit_{{ .User.Id }}" class="bi bi-pencil {{if not .User.HasPermissionEditOtherUploads}}perm-notgranted{{else}}perm-granted{{end}} {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}perm-nochange{{end}}" title="Edit other uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_EDIT", "perm_edit_{{ .User.Id }}");'></i>

<i id="perm_delete_{{ .User.Id }}" class="bi bi-trash3 {{if not .User.HasPermissionDeleteOtherUploads}}perm-notgranted{{else}}perm-granted{{end}} {{if eq .User.UserLevel 0}}perm-nochange{{end}}" title="Delete other uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_DELETE", "perm_delete_{{ .User.Id }}");'></i>
<i id="perm_delete_{{ .User.Id }}" class="bi bi-trash3 {{if not .User.HasPermissionDeleteOtherUploads}}perm-notgranted{{else}}perm-granted{{end}} {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}perm-nochange{{end}}" title="Delete other uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_DELETE", "perm_delete_{{ .User.Id }}");'></i>

<i id="perm_replace_other_{{ .User.Id }}" class="bi bi-arrow-left-right {{if not .User.HasPermissionReplaceOtherUploads}}perm-notgranted{{else}}perm-granted{{end}} {{if eq .User.UserLevel 0}}perm-nochange{{end}}" title="Replace other uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_REPLACE_OTHER", "perm_replace_other_{{ .User.Id }}");'></i>
<i id="perm_replace_other_{{ .User.Id }}" class="bi bi-arrow-left-right {{if not .User.HasPermissionReplaceOtherUploads}}perm-notgranted{{else}}perm-granted{{end}} {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}perm-nochange{{end}}" title="Replace other uploads" onclick='changeUserPermission("{{ .User.Id }}","PERM_REPLACE_OTHER", "perm_replace_other_{{ .User.Id }}");'></i>

<i id="perm_logs_{{ .User.Id }}" class="bi bi-card-list {{if not .User.HasPermissionManageLogs}}perm-notgranted{{else}}perm-granted{{end}} {{if eq .User.UserLevel 0}}perm-nochange{{end}}" title="Manage system logs" onclick='changeUserPermission("{{ .User.Id }}","PERM_LOGS", "perm_logs_{{ .User.Id }}");'></i>
<i id="perm_logs_{{ .User.Id }}" class="bi bi-card-list {{if not .User.HasPermissionManageLogs}}perm-notgranted{{else}}perm-granted{{end}} {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}perm-nochange{{end}}" title="Manage system logs" onclick='changeUserPermission("{{ .User.Id }}","PERM_LOGS", "perm_logs_{{ .User.Id }}");'></i>

<i id="perm_users_{{ .User.Id }}" class="bi bi-people {{if not .User.HasPermissionManageUsers}}perm-notgranted{{else}}perm-granted{{end}} {{if eq .User.UserLevel 0}}perm-nochange{{end}}" title="Manage users" onclick='changeUserPermission("{{ .User.Id }}","PERM_USERS", "perm_users_{{ .User.Id }}");'></i>
<i id="perm_users_{{ .User.Id }}" class="bi bi-people {{if not .User.HasPermissionManageUsers}}perm-notgranted{{else}}perm-granted{{end}} {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}perm-nochange{{end}}" title="Manage users" onclick='changeUserPermission("{{ .User.Id }}","PERM_USERS", "perm_users_{{ .User.Id }}");'></i>

<i id="perm_api_{{ .User.Id }}" class="bi bi-sliders2 {{if not .User.HasPermissionManageApi}}perm-notgranted{{else}}perm-granted{{end}} {{if eq .User.UserLevel 0}}perm-nochange{{end}}" title="Manage API keys" onclick='changeUserPermission("{{ .User.Id }}","PERM_API", "perm_api_{{ .User.Id }}");'></i>
<i id="perm_api_{{ .User.Id }}" class="bi bi-sliders2 {{if not .User.HasPermissionManageApi}}perm-notgranted{{else}}perm-granted{{end}} {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}perm-nochange{{end}}" title="Manage API keys" onclick='changeUserPermission("{{ .User.Id }}","PERM_API", "perm_api_{{ .User.Id }}");'></i>

</td>
<td scope="col">
{{if gt .User.UserLevel 1}}
<button id="changeRank_{{ .User.Id }}" type="button" onclick="changeRank({{ .User.Id }}, 'ADMIN', 'changeRank_{{ .User.Id }}')" title="Promote User" class="btn btn-outline-light btn-sm">
<button id="changeRank_{{ .User.Id }}" type="button" onclick="changeRank({{ .User.Id }}, 'ADMIN', 'changeRank_{{ .User.Id }}')" title="Promote User" {{ if eq .User.Id $.ActiveUser.Id}}disabled{{end}} class="btn btn-outline-light btn-sm">
<i class="bi bi-chevron-double-up"></i></button>
{{ else }}
<button id="changeRank_{{ .User.Id }}" type="button" onclick="changeRank({{ .User.Id }}, 'USER', 'changeRank_{{ .User.Id }}')" {{if eq .User.UserLevel 0}}disabled{{end}} title="Demote User" class="btn btn-outline-light btn-sm">
<button id="changeRank_{{ .User.Id }}" type="button" onclick="changeRank({{ .User.Id }}, 'USER', 'changeRank_{{ .User.Id }}')" {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}disabled{{end}} title="Demote User" class="btn btn-outline-light btn-sm">
<i class="bi bi-chevron-double-down"></i></button>
{{ end }}

<button id="delete-{{ .User.Id }}" type="button" class="btn btn-outline-danger btn-sm" {{if eq .User.UserLevel 0}}disabled{{end}} onclick="showDeleteModal('{{ .User.Id }}', '{{ .User.Email }}')" title="Delete"><i class="bi bi-trash3"></i></button></td>
<button id="delete-{{ .User.Id }}" type="button" class="btn btn-outline-danger btn-sm" {{if or (eq .User.UserLevel 0) (eq .User.Id $.ActiveUser.Id)}}disabled{{end}} onclick="showDeleteModal('{{ .User.Id }}', '{{ .User.Email }}')" title="Delete"><i class="bi bi-trash3"></i></button></td>
</tr>
{{ end }}
</tbody>
Expand Down

0 comments on commit 96dee6f

Please sign in to comment.