summaryrefslogtreecommitdiffstats
path: root/api4
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-07-31 12:59:32 -0400
committerGitHub <noreply@github.com>2017-07-31 12:59:32 -0400
commit59992ae4a4638006ec1489dd834151b258c1728c (patch)
tree8bc5c0fa8f6a4d6a40026c965bd865c1110af838 /api4
parented62660e96528920b0ecb8c755265c6c8d2756c4 (diff)
downloadchat-59992ae4a4638006ec1489dd834151b258c1728c.tar.gz
chat-59992ae4a4638006ec1489dd834151b258c1728c.tar.bz2
chat-59992ae4a4638006ec1489dd834151b258c1728c.zip
PLT-6763 Implement user access tokens and new roles (server-side) (#6972)
* Implement user access tokens and new roles * Update config.json * Add public post permission to apiv3 * Remove old comment * Fix model unit test * Updates to store per feedback * Updates per feedback from CS
Diffstat (limited to 'api4')
-rw-r--r--api4/context.go16
-rw-r--r--api4/params.go5
-rw-r--r--api4/post.go12
-rw-r--r--api4/post_test.go47
-rw-r--r--api4/user.go134
-rw-r--r--api4/user_test.go252
6 files changed, 464 insertions, 2 deletions
diff --git a/api4/context.go b/api4/context.go
index 61c318266..d72b3593d 100644
--- a/api4/context.go
+++ b/api4/context.go
@@ -239,6 +239,11 @@ func (c *Context) IsSystemAdmin() bool {
}
func (c *Context) SessionRequired() {
+ if !*utils.Cfg.ServiceSettings.EnableUserAccessTokens && c.Session.Props[model.SESSION_PROP_TYPE] == model.SESSION_TYPE_USER_ACCESS_TOKEN {
+ c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserAccessToken", http.StatusUnauthorized)
+ return
+ }
+
if len(c.Session.UserId) == 0 {
c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserRequired", http.StatusUnauthorized)
return
@@ -361,6 +366,17 @@ func (c *Context) RequireInviteId() *Context {
return c
}
+func (c *Context) RequireTokenId() *Context {
+ if c.Err != nil {
+ return c
+ }
+
+ if len(c.Params.TokenId) != 26 {
+ c.SetInvalidUrlParam("token_id")
+ }
+ return c
+}
+
func (c *Context) RequireChannelId() *Context {
if c.Err != nil {
return c
diff --git a/api4/params.go b/api4/params.go
index 999bf8e77..b48e5fc1b 100644
--- a/api4/params.go
+++ b/api4/params.go
@@ -20,6 +20,7 @@ type ApiParams struct {
UserId string
TeamId string
InviteId string
+ TokenId string
ChannelId string
PostId string
FileId string
@@ -60,6 +61,10 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams {
params.InviteId = val
}
+ if val, ok := props["token_id"]; ok {
+ params.TokenId = val
+ }
+
if val, ok := props["channel_id"]; ok {
params.ChannelId = val
}
diff --git a/api4/post.go b/api4/post.go
index 3d0c681d1..deaad1e1c 100644
--- a/api4/post.go
+++ b/api4/post.go
@@ -40,7 +40,17 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
post.UserId = c.Session.UserId
- if !app.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_CREATE_POST) {
+ hasPermission := false
+ if app.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_CREATE_POST) {
+ hasPermission = true
+ } else if channel, err := app.GetChannel(post.ChannelId); err == nil {
+ // Temporary permission check method until advanced permissions, please do not copy
+ if channel.Type == model.CHANNEL_OPEN && app.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_CREATE_POST_PUBLIC) {
+ hasPermission = true
+ }
+ }
+
+ if !hasPermission {
c.SetPermissionError(model.PERMISSION_CREATE_POST)
return
}
diff --git a/api4/post_test.go b/api4/post_test.go
index d554ca472..53babc6e6 100644
--- a/api4/post_test.go
+++ b/api4/post_test.go
@@ -197,7 +197,7 @@ func testCreatePostWithOutgoingHook(
if triggerWord != "" {
triggerWords = []string{triggerWord}
}
-
+
hook = &model.OutgoingWebhook{
ChannelId: channel.Id,
TeamId: team.Id,
@@ -257,6 +257,51 @@ func TestCreatePostWithOutgoingHook_no_content_type(t *testing.T) {
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_STARTS_WITH)
}
+func TestCreatePostPublic(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+
+ post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "#hashtag a" + model.NewId() + "a"}
+
+ user := model.User{Email: GenerateTestEmail(), Nickname: "Joram Wilander", Password: "hello1", Username: GenerateTestUsername(), Roles: model.ROLE_SYSTEM_USER.Id}
+
+ ruser, resp := Client.CreateUser(&user)
+ CheckNoError(t, resp)
+
+ Client.Login(user.Email, user.Password)
+
+ _, resp = Client.CreatePost(post)
+ CheckForbiddenStatus(t, resp)
+
+ app.UpdateUserRoles(ruser.Id, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_POST_ALL_PUBLIC.Id)
+ app.InvalidateAllCaches()
+
+ Client.Login(user.Email, user.Password)
+
+ _, resp = Client.CreatePost(post)
+ CheckNoError(t, resp)
+
+ post.ChannelId = th.BasicPrivateChannel.Id
+ _, resp = Client.CreatePost(post)
+ CheckForbiddenStatus(t, resp)
+
+ app.UpdateUserRoles(ruser.Id, model.ROLE_SYSTEM_USER.Id)
+ app.JoinUserToTeam(th.BasicTeam, ruser, "")
+ app.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, model.ROLE_TEAM_USER.Id+" "+model.ROLE_TEAM_POST_ALL_PUBLIC.Id)
+ app.InvalidateAllCaches()
+
+ Client.Login(user.Email, user.Password)
+
+ post.ChannelId = th.BasicPrivateChannel.Id
+ _, resp = Client.CreatePost(post)
+ CheckForbiddenStatus(t, resp)
+
+ post.ChannelId = th.BasicChannel.Id
+ _, resp = Client.CreatePost(post)
+ CheckNoError(t, resp)
+}
+
func TestUpdatePost(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
diff --git a/api4/user.go b/api4/user.go
index f13c33f0b..16c1f4a74 100644
--- a/api4/user.go
+++ b/api4/user.go
@@ -55,6 +55,11 @@ func InitUser() {
BaseRoutes.User.Handle("/sessions/revoke", ApiSessionRequired(revokeSession)).Methods("POST")
BaseRoutes.Users.Handle("/sessions/device", ApiSessionRequired(attachDeviceId)).Methods("PUT")
BaseRoutes.User.Handle("/audits", ApiSessionRequired(getUserAudits)).Methods("GET")
+
+ BaseRoutes.User.Handle("/tokens", ApiSessionRequired(createUserAccessToken)).Methods("POST")
+ BaseRoutes.User.Handle("/tokens", ApiSessionRequired(getUserAccessTokens)).Methods("GET")
+ BaseRoutes.Users.Handle("/tokens/{token_id:[A-Za-z0-9]+}", ApiSessionRequired(getUserAccessToken)).Methods("GET")
+ BaseRoutes.Users.Handle("/tokens/revoke", ApiSessionRequired(revokeUserAccessToken)).Methods("POST")
}
func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -1081,3 +1086,132 @@ func switchAccountType(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("success")
w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link})))
}
+
+func createUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireUserId()
+ if c.Err != nil {
+ return
+ }
+
+ accessToken := model.UserAccessTokenFromJson(r.Body)
+ if accessToken == nil {
+ c.SetInvalidParam("user_access_token")
+ return
+ }
+
+ if accessToken.Description == "" {
+ c.SetInvalidParam("description")
+ return
+ }
+
+ c.LogAudit("")
+
+ if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_CREATE_USER_ACCESS_TOKEN) {
+ c.SetPermissionError(model.PERMISSION_CREATE_USER_ACCESS_TOKEN)
+ return
+ }
+
+ if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) {
+ c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
+ return
+ }
+
+ accessToken.UserId = c.Params.UserId
+ accessToken.Token = ""
+
+ var err *model.AppError
+ accessToken, err = app.CreateUserAccessToken(accessToken)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ c.LogAudit("success - token_id=" + accessToken.Id)
+ w.Write([]byte(accessToken.ToJson()))
+}
+
+func getUserAccessTokens(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireUserId()
+ if c.Err != nil {
+ return
+ }
+
+ if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_READ_USER_ACCESS_TOKEN) {
+ c.SetPermissionError(model.PERMISSION_READ_USER_ACCESS_TOKEN)
+ return
+ }
+
+ if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) {
+ c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
+ return
+ }
+
+ accessTokens, err := app.GetUserAccessTokensForUser(c.Params.UserId, c.Params.Page, c.Params.PerPage)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ w.Write([]byte(model.UserAccessTokenListToJson(accessTokens)))
+}
+
+func getUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireTokenId()
+ if c.Err != nil {
+ return
+ }
+
+ if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_READ_USER_ACCESS_TOKEN) {
+ c.SetPermissionError(model.PERMISSION_READ_USER_ACCESS_TOKEN)
+ return
+ }
+
+ accessToken, err := app.GetUserAccessToken(c.Params.TokenId, true)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ if !app.SessionHasPermissionToUser(c.Session, accessToken.UserId) {
+ c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
+ return
+ }
+
+ w.Write([]byte(accessToken.ToJson()))
+}
+
+func revokeUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
+ props := model.MapFromJson(r.Body)
+ tokenId := props["token_id"]
+
+ if tokenId == "" {
+ c.SetInvalidParam("token_id")
+ }
+
+ c.LogAudit("")
+
+ if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_REVOKE_USER_ACCESS_TOKEN) {
+ c.SetPermissionError(model.PERMISSION_REVOKE_USER_ACCESS_TOKEN)
+ return
+ }
+
+ accessToken, err := app.GetUserAccessToken(tokenId, false)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ if !app.SessionHasPermissionToUser(c.Session, accessToken.UserId) {
+ c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
+ return
+ }
+
+ err = app.RevokeUserAccessToken(accessToken)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ c.LogAudit("success - token_id=" + accessToken.Id)
+ ReturnStatusOK(w)
+}
diff --git a/api4/user_test.go b/api4/user_test.go
index 77157e250..37ecd660d 100644
--- a/api4/user_test.go
+++ b/api4/user_test.go
@@ -2139,3 +2139,255 @@ func TestSwitchAccount(t *testing.T) {
_, resp = Client.SwitchAccountType(sr)
CheckUnauthorizedStatus(t, resp)
}
+
+func TestCreateUserAccessToken(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+ AdminClient := th.SystemAdminClient
+
+ testDescription := "test token"
+
+ enableUserAccessTokens := *utils.Cfg.ServiceSettings.EnableUserAccessTokens
+ defer func() {
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = enableUserAccessTokens
+ }()
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = true
+
+ _, resp := Client.CreateUserAccessToken(th.BasicUser.Id, testDescription)
+ CheckForbiddenStatus(t, resp)
+
+ _, resp = Client.CreateUserAccessToken("notarealuserid", testDescription)
+ CheckBadRequestStatus(t, resp)
+
+ _, resp = Client.CreateUserAccessToken(th.BasicUser.Id, "")
+ CheckBadRequestStatus(t, resp)
+
+ app.UpdateUserRoles(th.BasicUser.Id, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_USER_ACCESS_TOKEN.Id)
+
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = false
+ _, resp = Client.CreateUserAccessToken(th.BasicUser.Id, testDescription)
+ CheckNotImplementedStatus(t, resp)
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = true
+
+ rtoken, resp := Client.CreateUserAccessToken(th.BasicUser.Id, testDescription)
+ CheckNoError(t, resp)
+
+ if rtoken.UserId != th.BasicUser.Id {
+ t.Fatal("wrong user id")
+ } else if rtoken.Token == "" {
+ t.Fatal("token should not be empty")
+ } else if rtoken.Id == "" {
+ t.Fatal("id should not be empty")
+ } else if rtoken.Description != testDescription {
+ t.Fatal("description did not match")
+ }
+
+ oldSessionToken := Client.AuthToken
+ Client.AuthToken = rtoken.Token
+ ruser, resp := Client.GetMe("")
+ CheckNoError(t, resp)
+
+ if ruser.Id != th.BasicUser.Id {
+ t.Fatal("returned wrong user")
+ }
+
+ Client.AuthToken = oldSessionToken
+
+ _, resp = Client.CreateUserAccessToken(th.BasicUser2.Id, testDescription)
+ CheckForbiddenStatus(t, resp)
+
+ rtoken, resp = AdminClient.CreateUserAccessToken(th.BasicUser.Id, testDescription)
+ CheckNoError(t, resp)
+
+ if rtoken.UserId != th.BasicUser.Id {
+ t.Fatal("wrong user id")
+ }
+
+ oldSessionToken = Client.AuthToken
+ Client.AuthToken = rtoken.Token
+ ruser, resp = Client.GetMe("")
+ CheckNoError(t, resp)
+
+ if ruser.Id != th.BasicUser.Id {
+ t.Fatal("returned wrong user")
+ }
+}
+
+func TestGetUserAccessToken(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+ AdminClient := th.SystemAdminClient
+
+ testDescription := "test token"
+
+ enableUserAccessTokens := *utils.Cfg.ServiceSettings.EnableUserAccessTokens
+ defer func() {
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = enableUserAccessTokens
+ }()
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = true
+
+ _, resp := Client.GetUserAccessToken("123")
+ CheckBadRequestStatus(t, resp)
+
+ _, resp = Client.GetUserAccessToken(model.NewId())
+ CheckForbiddenStatus(t, resp)
+
+ app.UpdateUserRoles(th.BasicUser.Id, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_USER_ACCESS_TOKEN.Id)
+ token, resp := Client.CreateUserAccessToken(th.BasicUser.Id, testDescription)
+ CheckNoError(t, resp)
+
+ rtoken, resp := Client.GetUserAccessToken(token.Id)
+ CheckNoError(t, resp)
+
+ if rtoken.UserId != th.BasicUser.Id {
+ t.Fatal("wrong user id")
+ } else if rtoken.Token != "" {
+ t.Fatal("token should be blank")
+ } else if rtoken.Id == "" {
+ t.Fatal("id should not be empty")
+ } else if rtoken.Description != testDescription {
+ t.Fatal("description did not match")
+ }
+
+ _, resp = AdminClient.GetUserAccessToken(token.Id)
+ CheckNoError(t, resp)
+
+ token, resp = Client.CreateUserAccessToken(th.BasicUser.Id, testDescription)
+ CheckNoError(t, resp)
+
+ rtokens, resp := Client.GetUserAccessTokensForUser(th.BasicUser.Id, 0, 100)
+ CheckNoError(t, resp)
+
+ if len(rtokens) != 2 {
+ t.Fatal("should have 2 tokens")
+ }
+
+ for _, uat := range rtokens {
+ if uat.UserId != th.BasicUser.Id {
+ t.Fatal("wrong user id")
+ }
+ }
+
+ rtokens, resp = Client.GetUserAccessTokensForUser(th.BasicUser.Id, 1, 1)
+ CheckNoError(t, resp)
+
+ if len(rtokens) != 1 {
+ t.Fatal("should have 1 token")
+ }
+
+ rtokens, resp = AdminClient.GetUserAccessTokensForUser(th.BasicUser.Id, 0, 100)
+ CheckNoError(t, resp)
+
+ if len(rtokens) != 2 {
+ t.Fatal("should have 2 tokens")
+ }
+}
+
+func TestRevokeUserAccessToken(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+ AdminClient := th.SystemAdminClient
+
+ testDescription := "test token"
+
+ enableUserAccessTokens := *utils.Cfg.ServiceSettings.EnableUserAccessTokens
+ defer func() {
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = enableUserAccessTokens
+ }()
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = true
+
+ app.UpdateUserRoles(th.BasicUser.Id, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_USER_ACCESS_TOKEN.Id)
+ token, resp := Client.CreateUserAccessToken(th.BasicUser.Id, testDescription)
+ CheckNoError(t, resp)
+
+ oldSessionToken := Client.AuthToken
+ Client.AuthToken = token.Token
+ _, resp = Client.GetMe("")
+ CheckNoError(t, resp)
+ Client.AuthToken = oldSessionToken
+
+ ok, resp := Client.RevokeUserAccessToken(token.Id)
+ CheckNoError(t, resp)
+
+ if !ok {
+ t.Fatal("should have passed")
+ }
+
+ oldSessionToken = Client.AuthToken
+ Client.AuthToken = token.Token
+ _, resp = Client.GetMe("")
+ CheckUnauthorizedStatus(t, resp)
+ Client.AuthToken = oldSessionToken
+
+ token, resp = AdminClient.CreateUserAccessToken(th.BasicUser2.Id, testDescription)
+ CheckNoError(t, resp)
+
+ ok, resp = Client.RevokeUserAccessToken(token.Id)
+ CheckForbiddenStatus(t, resp)
+
+ if ok {
+ t.Fatal("should have failed")
+ }
+}
+
+func TestUserAccessTokenInactiveUser(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+
+ testDescription := "test token"
+
+ enableUserAccessTokens := *utils.Cfg.ServiceSettings.EnableUserAccessTokens
+ defer func() {
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = enableUserAccessTokens
+ }()
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = true
+
+ app.UpdateUserRoles(th.BasicUser.Id, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_USER_ACCESS_TOKEN.Id)
+ token, resp := Client.CreateUserAccessToken(th.BasicUser.Id, testDescription)
+ CheckNoError(t, resp)
+
+ Client.AuthToken = token.Token
+ _, resp = Client.GetMe("")
+ CheckNoError(t, resp)
+
+ app.UpdateActive(th.BasicUser, false)
+
+ _, resp = Client.GetMe("")
+ CheckUnauthorizedStatus(t, resp)
+}
+
+func TestUserAccessTokenDisableConfig(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+
+ testDescription := "test token"
+
+ enableUserAccessTokens := *utils.Cfg.ServiceSettings.EnableUserAccessTokens
+ defer func() {
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = enableUserAccessTokens
+ }()
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = true
+
+ app.UpdateUserRoles(th.BasicUser.Id, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_USER_ACCESS_TOKEN.Id)
+ token, resp := Client.CreateUserAccessToken(th.BasicUser.Id, testDescription)
+ CheckNoError(t, resp)
+
+ oldSessionToken := Client.AuthToken
+ Client.AuthToken = token.Token
+ _, resp = Client.GetMe("")
+ CheckNoError(t, resp)
+
+ *utils.Cfg.ServiceSettings.EnableUserAccessTokens = false
+
+ _, resp = Client.GetMe("")
+ CheckUnauthorizedStatus(t, resp)
+
+ Client.AuthToken = oldSessionToken
+ _, resp = Client.GetMe("")
+ CheckNoError(t, resp)
+}