diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index 4c76c026c6b..9030ba71876 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -384,8 +385,15 @@ func (hs *HTTPServer) AdminLogoutUser(c *contextmodel.ReqContext) response.Respo return response.Error(http.StatusBadRequest, "id is invalid", err) } - if c.UserID == userID { - return response.Error(400, "You cannot logout yourself", nil) + namespace, identifier := c.SignedInUser.GetNamespacedID() + if namespace == identity.NamespaceUser { + activeUserID, err := identity.IntIdentifier(namespace, identifier) + if err != nil { + return response.Error(http.StatusInternalServerError, "Failed to parse active user id", err) + } + if activeUserID == userID { + return response.Error(http.StatusBadRequest, "You cannot logout yourself", nil) + } } return hs.logoutUserFromAllDevicesInternal(c.Req.Context(), userID) diff --git a/pkg/api/apikey.go b/pkg/api/apikey.go index 894078633b6..2ca740ce5b0 100644 --- a/pkg/api/apikey.go +++ b/pkg/api/apikey.go @@ -32,11 +32,11 @@ import ( // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) GetAPIKeys(c *contextmodel.ReqContext) response.Response { - query := apikey.GetApiKeysQuery{OrgID: c.OrgID, User: c.SignedInUser, IncludeExpired: c.QueryBool("includeExpired")} + query := apikey.GetApiKeysQuery{OrgID: c.SignedInUser.GetOrgID(), User: c.SignedInUser, IncludeExpired: c.QueryBool("includeExpired")} keys, err := hs.apiKeyService.GetAPIKeys(c.Req.Context(), &query) if err != nil { - return response.Error(500, "Failed to list api keys", err) + return response.Error(http.StatusInternalServerError, "Failed to list api keys", err) } ids := map[string]bool{} @@ -87,7 +87,7 @@ func (hs *HTTPServer) DeleteAPIKey(c *contextmodel.ReqContext) response.Response return response.Error(http.StatusBadRequest, "id is invalid", err) } - cmd := &apikey.DeleteCommand{ID: id, OrgID: c.OrgID} + cmd := &apikey.DeleteCommand{ID: id, OrgID: c.SignedInUser.GetOrgID()} err = hs.apiKeyService.DeleteApiKey(c.Req.Context(), cmd) if err != nil { var status int @@ -126,38 +126,38 @@ func (hs *HTTPServer) AddAPIKey(c *contextmodel.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "bad request data", err) } if !cmd.Role.IsValid() { - return response.Error(400, "Invalid role specified", nil) + return response.Error(http.StatusBadRequest, "Invalid role specified", nil) } - if !c.OrgRole.Includes(cmd.Role) { + if !c.SignedInUser.GetOrgRole().Includes(cmd.Role) { return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil) } if hs.Cfg.ApiKeyMaxSecondsToLive != -1 { if cmd.SecondsToLive == 0 { - return response.Error(400, "Number of seconds before expiration should be set", nil) + return response.Error(http.StatusBadRequest, "Number of seconds before expiration should be set", nil) } if cmd.SecondsToLive > hs.Cfg.ApiKeyMaxSecondsToLive { - return response.Error(400, "Number of seconds before expiration is greater than the global limit", nil) + return response.Error(http.StatusBadRequest, "Number of seconds before expiration is greater than the global limit", nil) } } - cmd.OrgID = c.OrgID + cmd.OrgID = c.SignedInUser.GetOrgID() newKeyInfo, err := apikeygen.New(cmd.OrgID, cmd.Name) if err != nil { - return response.Error(500, "Generating API key failed", err) + return response.Error(http.StatusInternalServerError, "Generating API key failed", err) } cmd.Key = newKeyInfo.HashedKey key, err := hs.apiKeyService.AddAPIKey(c.Req.Context(), &cmd) if err != nil { if errors.Is(err, apikey.ErrInvalidExpiration) { - return response.Error(400, err.Error(), nil) + return response.Error(http.StatusBadRequest, err.Error(), nil) } if errors.Is(err, apikey.ErrDuplicate) { - return response.Error(409, err.Error(), nil) + return response.Error(http.StatusConflict, err.Error(), nil) } - return response.Error(500, "Failed to add API Key", err) + return response.Error(http.StatusInternalServerError, "Failed to add API Key", err) } result := &dtos.NewApiKeyResult{ diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 93a23a48a87..e98897119e1 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -214,6 +214,11 @@ func setupScenarioContext(t *testing.T, url string) *scenarioContext { return sc } +// FIXME: This user should not be anonymous +func authedUserWithPermissions(userID, orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser { + return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}} +} + // FIXME: This user should not be anonymous func userWithPermissions(orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser { return &user.SignedInUser{IsAnonymous: true, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}} diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index 95cd149b465..64e868239a7 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -142,8 +143,8 @@ func GetGravatarUrlWithDefault(text string, defaultText string) string { return GetGravatarUrl(text) } -func IsHiddenUser(userLogin string, signedInUser *user.SignedInUser, cfg *setting.Cfg) bool { - if userLogin == "" || signedInUser.IsGrafanaAdmin || userLogin == signedInUser.Login { +func IsHiddenUser(userLogin string, signedInUser identity.Requester, cfg *setting.Cfg) bool { + if userLogin == "" || signedInUser.GetIsGrafanaAdmin() || userLogin == signedInUser.GetLogin() { return false } diff --git a/pkg/api/org.go b/pkg/api/org.go index f32b6f83825..49a52e7cc78 100644 --- a/pkg/api/org.go +++ b/pkg/api/org.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/util" @@ -25,7 +26,7 @@ import ( // 403: forbiddenError // 500: internalServerError func (hs *HTTPServer) GetCurrentOrg(c *contextmodel.ReqContext) response.Response { - return hs.getOrgHelper(c.Req.Context(), c.OrgID) + return hs.getOrgHelper(c.Req.Context(), c.SignedInUser.GetOrgID()) } // swagger:route GET /orgs/{org_id} orgs getOrgByID @@ -131,7 +132,17 @@ func (hs *HTTPServer) CreateOrg(c *contextmodel.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "bad request data", err) } - cmd.UserID = c.UserID + namespace, identifier := c.SignedInUser.GetNamespacedID() + if namespace != identity.NamespaceUser { + return response.Error(http.StatusForbidden, "Only users can create organizations", nil) + } + + userID, err := identity.IntIdentifier(namespace, identifier) + if err != nil { + return response.Error(http.StatusInternalServerError, "Failed to parse user id", err) + } + + cmd.UserID = userID result, err := hs.orgService.CreateWithMember(c.Req.Context(), &cmd) if err != nil { if errors.Is(err, org.ErrOrgNameTaken) { @@ -163,7 +174,7 @@ func (hs *HTTPServer) UpdateCurrentOrg(c *contextmodel.ReqContext) response.Resp if err := web.Bind(c.Req, &form); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - return hs.updateOrgHelper(c.Req.Context(), form, c.OrgID) + return hs.updateOrgHelper(c.Req.Context(), form, c.SignedInUser.GetOrgID()) } // swagger:route PUT /orgs/{org_id} orgs updateOrg @@ -218,7 +229,7 @@ func (hs *HTTPServer) UpdateCurrentOrgAddress(c *contextmodel.ReqContext) respon if err := web.Bind(c.Req, &form); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - return hs.updateOrgAddressHelper(c.Req.Context(), form, c.OrgID) + return hs.updateOrgAddressHelper(c.Req.Context(), form, c.SignedInUser.GetOrgID()) } // swagger:route PUT /orgs/{org_id}/address orgs updateOrgAddress @@ -283,7 +294,7 @@ func (hs *HTTPServer) DeleteOrgByID(c *contextmodel.ReqContext) response.Respons return response.Error(http.StatusBadRequest, "orgId is invalid", err) } // before deleting an org, check if user does not belong to the current org - if c.OrgID == orgID { + if c.SignedInUser.GetOrgID() == orgID { return response.Error(http.StatusBadRequest, "Can not delete org for current user", nil) } diff --git a/pkg/api/org_test.go b/pkg/api/org_test.go index 54c7056c06d..777c82b4031 100644 --- a/pkg/api/org_test.go +++ b/pkg/api/org_test.go @@ -174,11 +174,11 @@ func TestAPIEndpoint_CreateOrgs(t *testing.T) { hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}} hs.accesscontrolService = actest.FakeService{} hs.userService = &usertest.FakeUserService{ - ExpectedSignedInUser: &user.SignedInUser{OrgID: 0}, + ExpectedSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 0}, } }) - req := webtest.RequestWithSignedInUser(server.NewPostRequest("/api/orgs", strings.NewReader(`{"name": "test"}`)), userWithPermissions(0, tt.permission)) + req := webtest.RequestWithSignedInUser(server.NewPostRequest("/api/orgs", strings.NewReader(`{"name": "test"}`)), authedUserWithPermissions(1, 0, tt.permission)) res, err := server.SendJSON(req) require.NoError(t, err) assert.Equal(t, tt.expectedCode, res.StatusCode) diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go index 1069bd71dd9..063d17db5aa 100644 --- a/pkg/api/org_users.go +++ b/pkg/api/org_users.go @@ -38,7 +38,7 @@ func (hs *HTTPServer) AddOrgUserToCurrentOrg(c *contextmodel.ReqContext) respons if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - cmd.OrgID = c.OrgID + cmd.OrgID = c.SignedInUser.GetOrgID() return hs.addOrgUserHelper(c, cmd) } @@ -72,28 +72,28 @@ func (hs *HTTPServer) AddOrgUser(c *contextmodel.ReqContext) response.Response { func (hs *HTTPServer) addOrgUserHelper(c *contextmodel.ReqContext, cmd org.AddOrgUserCommand) response.Response { if !cmd.Role.IsValid() { - return response.Error(400, "Invalid role specified", nil) + return response.Error(http.StatusBadRequest, "Invalid role specified", nil) } - if !c.OrgRole.Includes(cmd.Role) && !c.IsGrafanaAdmin { + if !c.SignedInUser.GetOrgRole().Includes(cmd.Role) && !c.SignedInUser.GetIsGrafanaAdmin() { return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil) } userQuery := user.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail} userToAdd, err := hs.userService.GetByLogin(c.Req.Context(), &userQuery) if err != nil { - return response.Error(404, "User not found", nil) + return response.Error(http.StatusNotFound, "User not found", nil) } cmd.UserID = userToAdd.ID if err := hs.orgService.AddOrgUser(c.Req.Context(), &cmd); err != nil { if errors.Is(err, org.ErrOrgUserAlreadyAdded) { - return response.JSON(409, util.DynMap{ + return response.JSON(http.StatusConflict, util.DynMap{ "message": "User is already member of this organization", "userId": cmd.UserID, }) } - return response.Error(500, "Could not add user to organization", err) + return response.Error(http.StatusInternalServerError, "Could not add user to organization", err) } return response.JSON(http.StatusOK, util.DynMap{ @@ -117,7 +117,7 @@ func (hs *HTTPServer) addOrgUserHelper(c *contextmodel.ReqContext, cmd org.AddOr // 500: internalServerError func (hs *HTTPServer) GetOrgUsersForCurrentOrg(c *contextmodel.ReqContext) response.Response { result, err := hs.searchOrgUsersHelper(c, &org.SearchOrgUsersQuery{ - OrgID: c.OrgID, + OrgID: c.SignedInUser.GetOrgID(), Query: c.Query("query"), Limit: c.QueryInt("limit"), User: c.SignedInUser, @@ -146,7 +146,7 @@ func (hs *HTTPServer) GetOrgUsersForCurrentOrg(c *contextmodel.ReqContext) respo func (hs *HTTPServer) GetOrgUsersForCurrentOrgLookup(c *contextmodel.ReqContext) response.Response { orgUsersResult, err := hs.searchOrgUsersHelper(c, &org.SearchOrgUsersQuery{ - OrgID: c.OrgID, + OrgID: c.SignedInUser.GetOrgID(), Query: c.Query("query"), Limit: c.QueryInt("limit"), User: c.SignedInUser, @@ -265,7 +265,7 @@ func (hs *HTTPServer) SearchOrgUsersWithPaging(c *contextmodel.ReqContext) respo } query := &org.SearchOrgUsersQuery{ - OrgID: c.OrgID, + OrgID: c.SignedInUser.GetOrgID(), Query: c.Query("query"), Page: page, Limit: perPage, @@ -351,7 +351,7 @@ func (hs *HTTPServer) UpdateOrgUserForCurrentOrg(c *contextmodel.ReqContext) res if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - cmd.OrgID = c.OrgID + cmd.OrgID = c.SignedInUser.GetOrgID() var err error cmd.UserID, err = strconv.ParseInt(web.Params(c.Req)[":userId"], 10, 64) if err != nil { @@ -394,7 +394,7 @@ func (hs *HTTPServer) updateOrgUserHelper(c *contextmodel.ReqContext, cmd org.Up if !cmd.Role.IsValid() { return response.Error(http.StatusBadRequest, "Invalid role specified", nil) } - if !c.OrgRole.Includes(cmd.Role) && !c.IsGrafanaAdmin { + if !c.SignedInUser.GetOrgRole().Includes(cmd.Role) && !c.SignedInUser.GetIsGrafanaAdmin() { return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil) } @@ -453,7 +453,7 @@ func (hs *HTTPServer) RemoveOrgUserForCurrentOrg(c *contextmodel.ReqContext) res return hs.removeOrgUserHelper(c.Req.Context(), &org.RemoveOrgUserCommand{ UserID: userId, - OrgID: c.OrgID, + OrgID: c.SignedInUser.GetOrgID(), ShouldDeleteOrphanedUser: true, }) } diff --git a/pkg/api/user.go b/pkg/api/user.go index 7ce89333105..0437baa5fbf 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -29,7 +30,11 @@ import ( // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) GetSignedInUser(c *contextmodel.ReqContext) response.Response { - return hs.getUserUserProfile(c, c.UserID) + userID, errResponse := getUserID(c) + if errResponse != nil { + return errResponse + } + return hs.getUserUserProfile(c, userID) } // swagger:route GET /users/{user_id} users getUserByID @@ -75,7 +80,7 @@ func (hs *HTTPServer) getUserUserProfile(c *contextmodel.ReqContext, userID int6 userProfile.IsGrafanaAdminExternallySynced = login.IsGrafanaAdminExternallySynced(hs.Cfg, authInfo.AuthModule, oAuthAndAllowAssignGrafanaAdmin) } - userProfile.AccessControl = hs.getAccessControlMetadata(c, c.OrgID, "global.users:id:", strconv.FormatInt(userID, 10)) + userProfile.AccessControl = hs.getAccessControlMetadata(c, c.SignedInUser.GetOrgID(), "global.users:id:", strconv.FormatInt(userID, 10)) userProfile.AvatarURL = dtos.GetGravatarUrl(userProfile.Email) return response.JSON(http.StatusOK, userProfile) @@ -133,15 +138,21 @@ func (hs *HTTPServer) UpdateSignedInUser(c *contextmodel.ReqContext) response.Re cmd.Email = strings.TrimSpace(cmd.Email) cmd.Login = strings.TrimSpace(cmd.Login) + userID, errResponse := getUserID(c) + if errResponse != nil { + return errResponse + } + if hs.Cfg.AuthProxyEnabled { - if hs.Cfg.AuthProxyHeaderProperty == "email" && cmd.Email != c.Email { - return response.Error(400, "Not allowed to change email when auth proxy is using email property", nil) + if hs.Cfg.AuthProxyHeaderProperty == "email" && cmd.Email != c.SignedInUser.GetEmail() { + return response.Error(http.StatusBadRequest, "Not allowed to change email when auth proxy is using email property", nil) } - if hs.Cfg.AuthProxyHeaderProperty == "username" && cmd.Login != c.Login { - return response.Error(400, "Not allowed to change username when auth proxy is using username property", nil) + if hs.Cfg.AuthProxyHeaderProperty == "username" && cmd.Login != c.SignedInUser.GetLogin() { + return response.Error(http.StatusBadRequest, "Not allowed to change username when auth proxy is using username property", nil) } } - cmd.UserID = c.UserID + + cmd.UserID = userID return hs.handleUpdateUser(c.Req.Context(), cmd) } @@ -256,7 +267,12 @@ func (hs *HTTPServer) isExternalUser(ctx context.Context, userID int64) (bool, e // 403: forbiddenError // 500: internalServerError func (hs *HTTPServer) GetSignedInUserOrgList(c *contextmodel.ReqContext) response.Response { - return hs.getUserOrgList(c.Req.Context(), c.UserID) + userID, errResponse := getUserID(c) + if errResponse != nil { + return errResponse + } + + return hs.getUserOrgList(c.Req.Context(), userID) } // swagger:route GET /user/teams signed_in_user getSignedInUserTeamList @@ -271,7 +287,12 @@ func (hs *HTTPServer) GetSignedInUserOrgList(c *contextmodel.ReqContext) respons // 403: forbiddenError // 500: internalServerError func (hs *HTTPServer) GetSignedInUserTeamList(c *contextmodel.ReqContext) response.Response { - return hs.getUserTeamList(c, c.OrgID, c.UserID) + userID, errResponse := getUserID(c) + if errResponse != nil { + return errResponse + } + + return hs.getUserTeamList(c, c.SignedInUser.GetOrgID(), userID) } // swagger:route GET /users/{user_id}/teams users getUserTeams @@ -291,7 +312,7 @@ func (hs *HTTPServer) GetUserTeams(c *contextmodel.ReqContext) response.Response if err != nil { return response.Error(http.StatusBadRequest, "id is invalid", err) } - return hs.getUserTeamList(c, c.OrgID, id) + return hs.getUserTeamList(c, c.SignedInUser.GetOrgID(), id) } func (hs *HTTPServer) getUserTeamList(c *contextmodel.ReqContext, orgID int64, userID int64) response.Response { @@ -299,7 +320,7 @@ func (hs *HTTPServer) getUserTeamList(c *contextmodel.ReqContext, orgID int64, u queryResult, err := hs.teamService.GetTeamsByUser(c.Req.Context(), &query) if err != nil { - return response.Error(500, "Failed to get user teams", err) + return response.Error(http.StatusInternalServerError, "Failed to get user teams", err) } for _, team := range queryResult { @@ -333,7 +354,7 @@ func (hs *HTTPServer) getUserOrgList(ctx context.Context, userID int64) response result, err := hs.orgService.GetUserOrgList(ctx, &query) if err != nil { - return response.Error(500, "Failed to get user organizations", err) + return response.Error(http.StatusInternalServerError, "Failed to get user organizations", err) } return response.JSON(http.StatusOK, result) @@ -376,14 +397,19 @@ func (hs *HTTPServer) UserSetUsingOrg(c *contextmodel.ReqContext) response.Respo return response.Error(http.StatusBadRequest, "id is invalid", err) } - if !hs.validateUsingOrg(c.Req.Context(), c.UserID, orgID) { - return response.Error(401, "Not a valid organization", nil) + userID, errResponse := getUserID(c) + if errResponse != nil { + return errResponse + } + + if !hs.validateUsingOrg(c.Req.Context(), userID, orgID) { + return response.Error(http.StatusUnauthorized, "Not a valid organization", nil) } - cmd := user.SetUsingOrgCommand{UserID: c.UserID, OrgID: orgID} + cmd := user.SetUsingOrgCommand{UserID: userID, OrgID: orgID} if err := hs.userService.SetUsingOrg(c.Req.Context(), &cmd); err != nil { - return response.Error(500, "Failed to change active organization", err) + return response.Error(http.StatusInternalServerError, "Failed to change active organization", err) } return response.Success("Active organization changed") @@ -397,14 +423,27 @@ func (hs *HTTPServer) ChangeActiveOrgAndRedirectToHome(c *contextmodel.ReqContex return } - if !hs.validateUsingOrg(c.Req.Context(), c.UserID, orgID) { - hs.NotFoundHandler(c) + namespace, identifier := c.SignedInUser.GetNamespacedID() + if namespace != identity.NamespaceUser { + c.JsonApiErr(http.StatusForbidden, "Endpoint only available for users", nil) + return } - cmd := user.SetUsingOrgCommand{UserID: c.UserID, OrgID: orgID} + userID, err := identity.IntIdentifier(namespace, identifier) + if err != nil { + c.JsonApiErr(http.StatusInternalServerError, "Failed to parse user id", err) + return + } + if !hs.validateUsingOrg(c.Req.Context(), userID, orgID) { + hs.NotFoundHandler(c) + return + } + + cmd := user.SetUsingOrgCommand{UserID: userID, OrgID: orgID} if err := hs.userService.SetUsingOrg(c.Req.Context(), &cmd); err != nil { hs.NotFoundHandler(c) + return } c.Redirect(hs.Cfg.AppSubURL + "/") @@ -431,42 +470,47 @@ func (hs *HTTPServer) ChangeUserPassword(c *contextmodel.ReqContext) response.Re return response.Error(http.StatusBadRequest, "bad request data", err) } - userQuery := user.GetUserByIDQuery{ID: c.UserID} + userID, errResponse := getUserID(c) + if errResponse != nil { + return errResponse + } + + userQuery := user.GetUserByIDQuery{ID: userID} usr, err := hs.userService.GetByID(c.Req.Context(), &userQuery) if err != nil { - return response.Error(500, "Could not read user from database", err) + return response.Error(http.StatusInternalServerError, "Could not read user from database", err) } getAuthQuery := login.GetAuthInfoQuery{UserId: usr.ID} if authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &getAuthQuery); err == nil { authModule := authInfo.AuthModule if authModule == login.LDAPAuthModule || authModule == login.AuthProxyAuthModule { - return response.Error(400, "Not allowed to reset password for LDAP or Auth Proxy user", nil) + return response.Error(http.StatusBadRequest, "Not allowed to reset password for LDAP or Auth Proxy user", nil) } } passwordHashed, err := util.EncodePassword(cmd.OldPassword, usr.Salt) if err != nil { - return response.Error(500, "Failed to encode password", err) + return response.Error(http.StatusInternalServerError, "Failed to encode password", err) } if passwordHashed != usr.Password { - return response.Error(401, "Invalid old password", nil) + return response.Error(http.StatusUnauthorized, "Invalid old password", nil) } password := user.Password(cmd.NewPassword) if password.IsWeak() { - return response.Error(400, "New password is too short", nil) + return response.Error(http.StatusBadRequest, "New password is too short", nil) } - cmd.UserID = c.UserID + cmd.UserID = userID cmd.NewPassword, err = util.EncodePassword(cmd.NewPassword, usr.Salt) if err != nil { - return response.Error(500, "Failed to encode password", err) + return response.Error(http.StatusInternalServerError, "Failed to encode password", err) } if err := hs.userService.ChangePassword(c.Req.Context(), &cmd); err != nil { - return response.Error(500, "Failed to change user password", err) + return response.Error(http.StatusInternalServerError, "Failed to change user password", err) } return response.Success("User password changed") @@ -492,16 +536,26 @@ func (hs *HTTPServer) SetHelpFlag(c *contextmodel.ReqContext) response.Response return response.Error(http.StatusBadRequest, "id is invalid", err) } - bitmask := &c.HelpFlags1 + userID, errResponse := getUserID(c) + if errResponse != nil { + return errResponse + } + + usr, err := hs.userService.GetByID(c.Req.Context(), &user.GetUserByIDQuery{ID: userID}) + if err != nil { + return response.Error(http.StatusInternalServerError, "Failed to get user", err) + } + + bitmask := &usr.HelpFlags1 bitmask.AddFlag(user.HelpFlags1(flag)) cmd := user.SetUserHelpFlagCommand{ - UserID: c.UserID, + UserID: userID, HelpFlags1: *bitmask, } if err := hs.userService.SetUserHelpFlag(c.Req.Context(), &cmd); err != nil { - return response.Error(500, "Failed to update help flag", err) + return response.Error(http.StatusInternalServerError, "Failed to update help flag", err) } return response.JSON(http.StatusOK, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1}) @@ -517,8 +571,13 @@ func (hs *HTTPServer) SetHelpFlag(c *contextmodel.ReqContext) response.Response // 403: forbiddenError // 500: internalServerError func (hs *HTTPServer) ClearHelpFlags(c *contextmodel.ReqContext) response.Response { + userID, errResponse := getUserID(c) + if errResponse != nil { + return errResponse + } + cmd := user.SetUserHelpFlagCommand{ - UserID: c.UserID, + UserID: userID, HelpFlags1: user.HelpFlags1(0), } @@ -529,6 +588,20 @@ func (hs *HTTPServer) ClearHelpFlags(c *contextmodel.ReqContext) response.Respon return response.JSON(http.StatusOK, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1}) } +func getUserID(c *contextmodel.ReqContext) (int64, *response.NormalResponse) { + namespace, identifier := c.SignedInUser.GetNamespacedID() + if namespace != identity.NamespaceUser { + return 0, response.Error(http.StatusForbidden, "Endpoint only available for users", nil) + } + + userID, err := identity.IntIdentifier(namespace, identifier) + if err != nil { + return 0, response.Error(http.StatusInternalServerError, "Failed to parse user id", err) + } + + return userID, nil +} + // swagger:parameters searchUsers type SearchUsersParams struct { // Limit the maximum number of users to return per page diff --git a/pkg/api/user_token.go b/pkg/api/user_token.go index 5366d316388..ece71fe617c 100644 --- a/pkg/api/user_token.go +++ b/pkg/api/user_token.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/network" "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/user" @@ -31,7 +32,17 @@ import ( // 403: forbiddenError // 500: internalServerError func (hs *HTTPServer) GetUserAuthTokens(c *contextmodel.ReqContext) response.Response { - return hs.getUserAuthTokensInternal(c, c.UserID) + namespace, identifier := c.SignedInUser.GetNamespacedID() + if namespace != identity.NamespaceUser { + return response.Error(http.StatusForbidden, "entity not allowed to revoke tokens", nil) + } + + userID, err := identity.IntIdentifier(namespace, identifier) + if err != nil { + return response.Error(http.StatusInternalServerError, "failed to parse user id", err) + } + + return hs.getUserAuthTokensInternal(c, userID) } // swagger:route POST /user/revoke-auth-token signed_in_user revokeUserAuthToken @@ -51,7 +62,18 @@ func (hs *HTTPServer) RevokeUserAuthToken(c *contextmodel.ReqContext) response.R if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - return hs.revokeUserAuthTokenInternal(c, c.UserID, cmd) + + namespace, identifier := c.SignedInUser.GetNamespacedID() + if namespace != identity.NamespaceUser { + return response.Error(http.StatusForbidden, "entity not allowed to revoke tokens", nil) + } + + userID, err := identity.IntIdentifier(namespace, identifier) + if err != nil { + return response.Error(http.StatusInternalServerError, "failed to parse user id", err) + } + + return hs.revokeUserAuthTokenInternal(c, userID, cmd) } func (hs *HTTPServer) RotateUserAuthTokenRedirect(c *contextmodel.ReqContext) response.Response { diff --git a/pkg/services/apikey/model.go b/pkg/services/apikey/model.go index 1414b672287..fe6c7b03690 100644 --- a/pkg/services/apikey/model.go +++ b/pkg/services/apikey/model.go @@ -4,9 +4,9 @@ import ( "errors" "time" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/quota" - "github.com/grafana/grafana/pkg/services/user" ) var ( @@ -50,7 +50,7 @@ type DeleteCommand struct { type GetApiKeysQuery struct { OrgID int64 IncludeExpired bool - User *user.SignedInUser + User identity.Requester } type GetByNameQuery struct { KeyName string diff --git a/pkg/services/auth/identity/requester.go b/pkg/services/auth/identity/requester.go index 6b0ba901024..ddab006f8e2 100644 --- a/pkg/services/auth/identity/requester.go +++ b/pkg/services/auth/identity/requester.go @@ -33,7 +33,7 @@ type Requester interface { GetLogin() string // GetNamespacedID returns the namespace and ID of the active entity. // The namespace is one of the constants defined in pkg/services/auth/identity. - GetNamespacedID() (string, string) + GetNamespacedID() (namespace string, identifier string) // GetOrgID returns the ID of the active organization GetOrgID() int64 // GetOrgRole returns the role of the active entity in the active organization.