summaryrefslogtreecommitdiffstats
path: root/model
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 /model
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 'model')
-rw-r--r--model/authorization.go69
-rw-r--r--model/client4.go58
-rw-r--r--model/config.go6
-rw-r--r--model/session.go22
-rw-r--r--model/user_access_token.go81
-rw-r--r--model/user_access_token_test.go58
6 files changed, 284 insertions, 10 deletions
diff --git a/model/authorization.go b/model/authorization.go
index 880d25e27..cf7e2b481 100644
--- a/model/authorization.go
+++ b/model/authorization.go
@@ -48,6 +48,7 @@ var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission
var PERMISSION_MANAGE_OAUTH *Permission
var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission
var PERMISSION_CREATE_POST *Permission
+var PERMISSION_CREATE_POST_PUBLIC *Permission
var PERMISSION_EDIT_POST *Permission
var PERMISSION_EDIT_OTHERS_POSTS *Permission
var PERMISSION_DELETE_POST *Permission
@@ -59,6 +60,9 @@ var PERMISSION_IMPORT_TEAM *Permission
var PERMISSION_VIEW_TEAM *Permission
var PERMISSION_LIST_USERS_WITHOUT_TEAM *Permission
var PERMISSION_MANAGE_JOBS *Permission
+var PERMISSION_CREATE_USER_ACCESS_TOKEN *Permission
+var PERMISSION_READ_USER_ACCESS_TOKEN *Permission
+var PERMISSION_REVOKE_USER_ACCESS_TOKEN *Permission
// General permission that encompases all system admin functions
// in the future this could be broken up to allow access to some
@@ -67,9 +71,12 @@ var PERMISSION_MANAGE_SYSTEM *Permission
var ROLE_SYSTEM_USER *Role
var ROLE_SYSTEM_ADMIN *Role
+var ROLE_SYSTEM_POST_ALL_PUBLIC *Role
+var ROLE_SYSTEM_USER_ACCESS_TOKEN *Role
var ROLE_TEAM_USER *Role
var ROLE_TEAM_ADMIN *Role
+var ROLE_TEAM_POST_ALL_PUBLIC *Role
var ROLE_CHANNEL_USER *Role
var ROLE_CHANNEL_ADMIN *Role
@@ -243,6 +250,11 @@ func InitalizePermissions() {
"authentication.permissions.create_post.name",
"authentication.permissions.create_post.description",
}
+ PERMISSION_CREATE_POST_PUBLIC = &Permission{
+ "create_post_public",
+ "authentication.permissions.create_post_public.name",
+ "authentication.permissions.create_post_public.description",
+ }
PERMISSION_EDIT_POST = &Permission{
"edit_post",
"authentication.permissions.edit_post.name",
@@ -290,8 +302,23 @@ func InitalizePermissions() {
}
PERMISSION_LIST_USERS_WITHOUT_TEAM = &Permission{
"list_users_without_team",
- "authentication.permisssions.list_users_without_team.name",
- "authentication.permisssions.list_users_without_team.description",
+ "authentication.permissions.list_users_without_team.name",
+ "authentication.permissions.list_users_without_team.description",
+ }
+ PERMISSION_CREATE_USER_ACCESS_TOKEN = &Permission{
+ "create_user_access_token",
+ "authentication.permissions.create_user_access_token.name",
+ "authentication.permissions.create_user_access_token.description",
+ }
+ PERMISSION_READ_USER_ACCESS_TOKEN = &Permission{
+ "read_user_access_token",
+ "authentication.permissions.read_user_access_token.name",
+ "authentication.permissions.read_user_access_token.description",
+ }
+ PERMISSION_REVOKE_USER_ACCESS_TOKEN = &Permission{
+ "revoke_user_access_token",
+ "authentication.permissions.revoke_user_access_token.name",
+ "authentication.permissions.revoke_user_access_token.description",
}
PERMISSION_MANAGE_JOBS = &Permission{
"manage_jobs",
@@ -348,6 +375,17 @@ func InitalizeRoles() {
},
}
BuiltInRoles[ROLE_TEAM_USER.Id] = ROLE_TEAM_USER
+
+ ROLE_TEAM_POST_ALL_PUBLIC = &Role{
+ "team_post_all_public",
+ "authentication.roles.team_post_all_public.name",
+ "authentication.roles.team_post_all_public.description",
+ []string{
+ PERMISSION_CREATE_POST_PUBLIC.Id,
+ },
+ }
+ BuiltInRoles[ROLE_TEAM_POST_ALL_PUBLIC.Id] = ROLE_TEAM_POST_ALL_PUBLIC
+
ROLE_TEAM_ADMIN = &Role{
"team_admin",
"authentication.roles.team_admin.name",
@@ -378,6 +416,29 @@ func InitalizeRoles() {
},
}
BuiltInRoles[ROLE_SYSTEM_USER.Id] = ROLE_SYSTEM_USER
+
+ ROLE_SYSTEM_POST_ALL_PUBLIC = &Role{
+ "system_post_all_public",
+ "authentication.roles.system_post_all_public.name",
+ "authentication.roles.system_post_all_public.description",
+ []string{
+ PERMISSION_CREATE_POST_PUBLIC.Id,
+ },
+ }
+ BuiltInRoles[ROLE_SYSTEM_POST_ALL_PUBLIC.Id] = ROLE_SYSTEM_POST_ALL_PUBLIC
+
+ ROLE_SYSTEM_USER_ACCESS_TOKEN = &Role{
+ "system_user_access_token",
+ "authentication.roles.system_user_access_token.name",
+ "authentication.roles.system_user_access_token.description",
+ []string{
+ PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
+ PERMISSION_READ_USER_ACCESS_TOKEN.Id,
+ PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
+ },
+ }
+ BuiltInRoles[ROLE_SYSTEM_USER_ACCESS_TOKEN.Id] = ROLE_SYSTEM_USER_ACCESS_TOKEN
+
ROLE_SYSTEM_ADMIN = &Role{
"system_admin",
"authentication.roles.global_admin.name",
@@ -412,6 +473,10 @@ func InitalizeRoles() {
PERMISSION_ADD_USER_TO_TEAM.Id,
PERMISSION_LIST_USERS_WITHOUT_TEAM.Id,
PERMISSION_MANAGE_JOBS.Id,
+ PERMISSION_CREATE_POST_PUBLIC.Id,
+ PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
+ PERMISSION_READ_USER_ACCESS_TOKEN.Id,
+ PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
},
ROLE_TEAM_USER.Permissions...,
),
diff --git a/model/client4.go b/model/client4.go
index 6f5eb03c6..2daca4dc9 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -70,6 +70,10 @@ func (c *Client4) GetUserRoute(userId string) string {
return fmt.Sprintf(c.GetUsersRoute()+"/%v", userId)
}
+func (c *Client4) GetUserAccessTokenRoute(tokenId string) string {
+ return fmt.Sprintf(c.GetUsersRoute()+"/tokens/%v", tokenId)
+}
+
func (c *Client4) GetUserByUsernameRoute(userName string) string {
return fmt.Sprintf(c.GetUsersRoute()+"/username/%v", userName)
}
@@ -957,6 +961,60 @@ func (c *Client4) SetProfileImage(userId string, data []byte) (bool, *Response)
}
}
+// CreateUserAccessToken will generate a user access token that can be used in place
+// of a session token to access the REST API. Must have the 'create_user_access_token'
+// permission and if generating for another user, must have the 'edit_other_users'
+// permission. A non-blank description is required.
+func (c *Client4) CreateUserAccessToken(userId, description string) (*UserAccessToken, *Response) {
+ requestBody := map[string]string{"description": description}
+ if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/tokens", MapToJson(requestBody)); err != nil {
+ return nil, BuildErrorResponse(r, err)
+ } else {
+ defer closeBody(r)
+ return UserAccessTokenFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetUserAccessToken will get a user access token's id, description and the user_id
+// of the user it is for. The actual token will not be returned. Must have the
+// 'read_user_access_token' permission and if getting for another user, must have the
+// 'edit_other_users' permission.
+func (c *Client4) GetUserAccessToken(tokenId string) (*UserAccessToken, *Response) {
+ if r, err := c.DoApiGet(c.GetUserAccessTokenRoute(tokenId), ""); err != nil {
+ return nil, BuildErrorResponse(r, err)
+ } else {
+ defer closeBody(r)
+ return UserAccessTokenFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetUserAccessTokensForUser will get a paged list of user access tokens showing id,
+// description and user_id for each. The actual tokens will not be returned. Must have
+// the 'read_user_access_token' permission and if getting for another user, must have the
+// 'edit_other_users' permission.
+func (c *Client4) GetUserAccessTokensForUser(userId string, page, perPage int) ([]*UserAccessToken, *Response) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/tokens"+query, ""); err != nil {
+ return nil, BuildErrorResponse(r, err)
+ } else {
+ defer closeBody(r)
+ return UserAccessTokenListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// RevokeUserAccessToken will revoke a user access token by id. Must have the
+// 'revoke_user_access_token' permission and if revoking for another user, must have the
+// 'edit_other_users' permission.
+func (c *Client4) RevokeUserAccessToken(tokenId string) (bool, *Response) {
+ requestBody := map[string]string{"token_id": tokenId}
+ if r, err := c.DoApiPost(c.GetUsersRoute()+"/tokens/revoke", MapToJson(requestBody)); err != nil {
+ return false, BuildErrorResponse(r, err)
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
// Team Section
// CreateTeam creates a team in the system based on the provided team struct.
diff --git a/model/config.go b/model/config.go
index 475e512f9..0755f514f 100644
--- a/model/config.go
+++ b/model/config.go
@@ -154,6 +154,7 @@ type ServiceSettings struct {
EnableInsecureOutgoingConnections *bool
EnableMultifactorAuthentication *bool
EnforceMultifactorAuthentication *bool
+ EnableUserAccessTokens *bool
AllowCorsFrom *string
SessionLengthWebInDays *int
SessionLengthMobileInDays *int
@@ -618,6 +619,11 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.EnforceMultifactorAuthentication = false
}
+ if o.ServiceSettings.EnableUserAccessTokens == nil {
+ o.ServiceSettings.EnableUserAccessTokens = new(bool)
+ *o.ServiceSettings.EnableUserAccessTokens = false
+ }
+
if o.PasswordSettings.MinimumLength == nil {
o.PasswordSettings.MinimumLength = new(int)
*o.PasswordSettings.MinimumLength = PASSWORD_MINIMUM_LENGTH
diff --git a/model/session.go b/model/session.go
index 4f3547582..960c18cbf 100644
--- a/model/session.go
+++ b/model/session.go
@@ -10,13 +10,17 @@ import (
)
const (
- SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
- SESSION_COOKIE_USER = "MMUSERID"
- SESSION_CACHE_SIZE = 35000
- SESSION_PROP_PLATFORM = "platform"
- SESSION_PROP_OS = "os"
- SESSION_PROP_BROWSER = "browser"
- SESSION_ACTIVITY_TIMEOUT = 1000 * 60 * 5 // 5 minutes
+ SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
+ SESSION_COOKIE_USER = "MMUSERID"
+ SESSION_CACHE_SIZE = 35000
+ SESSION_PROP_PLATFORM = "platform"
+ SESSION_PROP_OS = "os"
+ SESSION_PROP_BROWSER = "browser"
+ SESSION_PROP_TYPE = "type"
+ SESSION_PROP_USER_ACCESS_TOKEN_ID = "user_access_token_id"
+ SESSION_TYPE_USER_ACCESS_TOKEN = "UserAccessToken"
+ SESSION_ACTIVITY_TIMEOUT = 1000 * 60 * 5 // 5 minutes
+ SESSION_USER_ACCESS_TOKEN_EXPIRY = 100 * 365 // 100 years
)
type Session struct {
@@ -58,7 +62,9 @@ func (me *Session) PreSave() {
me.Id = NewId()
}
- me.Token = NewId()
+ if me.Token == "" {
+ me.Token = NewId()
+ }
me.CreateAt = GetMillis()
me.LastActivityAt = me.CreateAt
diff --git a/model/user_access_token.go b/model/user_access_token.go
new file mode 100644
index 000000000..090780fd0
--- /dev/null
+++ b/model/user_access_token.go
@@ -0,0 +1,81 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+ "net/http"
+)
+
+type UserAccessToken struct {
+ Id string `json:"id"`
+ Token string `json:"token,omitempty"`
+ UserId string `json:"user_id"`
+ Description string `json:"description"`
+}
+
+func (t *UserAccessToken) IsValid() *AppError {
+ if len(t.Id) != 26 {
+ return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if len(t.Token) != 26 {
+ return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.token.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if len(t.UserId) != 26 {
+ return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if len(t.Description) > 255 {
+ return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.description.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (t *UserAccessToken) PreSave() {
+ t.Id = NewId()
+}
+
+func (t *UserAccessToken) ToJson() string {
+ b, err := json.Marshal(t)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func UserAccessTokenFromJson(data io.Reader) *UserAccessToken {
+ decoder := json.NewDecoder(data)
+ var t UserAccessToken
+ err := decoder.Decode(&t)
+ if err == nil {
+ return &t
+ } else {
+ return nil
+ }
+}
+
+func UserAccessTokenListToJson(t []*UserAccessToken) string {
+ b, err := json.Marshal(t)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func UserAccessTokenListFromJson(data io.Reader) []*UserAccessToken {
+ decoder := json.NewDecoder(data)
+ var t []*UserAccessToken
+ err := decoder.Decode(&t)
+ if err == nil {
+ return t
+ } else {
+ return nil
+ }
+}
diff --git a/model/user_access_token_test.go b/model/user_access_token_test.go
new file mode 100644
index 000000000..1b4a9ccfd
--- /dev/null
+++ b/model/user_access_token_test.go
@@ -0,0 +1,58 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestUserAccessTokenJson(t *testing.T) {
+ a1 := UserAccessToken{}
+ a1.UserId = NewId()
+ a1.Token = NewId()
+
+ json := a1.ToJson()
+ ra1 := UserAccessTokenFromJson(strings.NewReader(json))
+
+ if a1.Token != ra1.Token {
+ t.Fatal("tokens didn't match")
+ }
+
+ tokens := []*UserAccessToken{&a1}
+ json = UserAccessTokenListToJson(tokens)
+ tokens = UserAccessTokenListFromJson(strings.NewReader(json))
+
+ if tokens[0].Token != a1.Token {
+ t.Fatal("tokens didn't match")
+ }
+}
+
+func TestUserAccessTokenIsValid(t *testing.T) {
+ ad := UserAccessToken{}
+
+ if err := ad.IsValid(); err == nil || err.Id != "model.user_access_token.is_valid.id.app_error" {
+ t.Fatal(err)
+ }
+
+ ad.Id = NewRandomString(26)
+ if err := ad.IsValid(); err == nil || err.Id != "model.user_access_token.is_valid.token.app_error" {
+ t.Fatal(err)
+ }
+
+ ad.Token = NewRandomString(26)
+ if err := ad.IsValid(); err == nil || err.Id != "model.user_access_token.is_valid.user_id.app_error" {
+ t.Fatal(err)
+ }
+
+ ad.UserId = NewRandomString(26)
+ if err := ad.IsValid(); err != nil {
+ t.Fatal(err)
+ }
+
+ ad.Description = NewRandomString(256)
+ if err := ad.IsValid(); err == nil || err.Id != "model.user_access_token.is_valid.description.app_error" {
+ t.Fatal(err)
+ }
+}