1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-07-15 23:54:29 +02:00

Channels style UUID (#1369)

* server channels style uuids
* webapp channels style uuids
This commit is contained in:
Doug Lauder
2021-10-05 09:52:59 -04:00
committed by GitHub
parent c30c17f684
commit 4feafb9806
28 changed files with 204 additions and 102 deletions

View File

@ -1170,7 +1170,7 @@ func (a *API) handlePostWorkspaceRegenerateSignupToken(w http.ResponseWriter, r
auditRec := a.makeAuditRecord(r, "regenerateSignupToken", audit.Fail) auditRec := a.makeAuditRecord(r, "regenerateSignupToken", audit.Fail)
defer a.audit.LogRecord(audit.LevelModify, auditRec) defer a.audit.LogRecord(audit.LevelModify, auditRec)
workspace.SignupToken = utils.CreateGUID() workspace.SignupToken = utils.NewID(utils.IDTypeToken)
err = a.app.UpsertWorkspaceSignupToken(*workspace) err = a.app.UpsertWorkspaceSignupToken(*workspace)
if err != nil { if err != nil {

View File

@ -1,10 +1,10 @@
package app package app
import ( import (
"github.com/google/uuid"
"github.com/mattermost/focalboard/server/model" "github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/auth" "github.com/mattermost/focalboard/server/services/auth"
"github.com/mattermost/focalboard/server/services/store" "github.com/mattermost/focalboard/server/services/store"
"github.com/mattermost/focalboard/server/utils"
"github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/shared/mlog"
@ -102,8 +102,8 @@ func (a *App) Login(username, email, password, mfaToken string) (string, error)
} }
session := model.Session{ session := model.Session{
ID: uuid.New().String(), ID: utils.NewID(utils.IDTypeSession),
Token: uuid.New().String(), Token: utils.NewID(utils.IDTypeToken),
UserID: user.ID, UserID: user.ID,
AuthService: authService, AuthService: authService,
Props: map[string]interface{}{}, Props: map[string]interface{}{},
@ -149,7 +149,7 @@ func (a *App) RegisterUser(username, email, password string) error {
} }
err = a.store.CreateUser(&model.User{ err = a.store.CreateUser(&model.User{
ID: uuid.New().String(), ID: utils.NewID(utils.IDTypeUser),
Username: username, Username: username,
Email: email, Email: email,
Password: auth.HashPassword(password), Password: auth.HashPassword(password),

View File

@ -13,7 +13,7 @@ import (
) )
var mockUser = &model.User{ var mockUser = &model.User{
ID: utils.CreateGUID(), ID: utils.NewID(utils.IDTypeUser),
Username: "testUsername", Username: "testUsername",
Email: "testEmail", Email: "testEmail",
Password: auth.HashPassword("testPassword"), Password: auth.HashPassword("testPassword"),

View File

@ -20,7 +20,7 @@ func (a *App) SaveFile(reader io.Reader, workspaceID, rootID, filename string) (
fileExtension = ".jpg" fileExtension = ".jpg"
} }
createdFilename := fmt.Sprintf(`%s%s`, utils.CreateGUID(), fileExtension) createdFilename := fmt.Sprintf(`%s%s`, utils.NewID(utils.IDTypeNone), fileExtension)
filePath := filepath.Join(workspaceID, rootID, createdFilename) filePath := filepath.Join(workspaceID, rootID, createdFilename)
_, appErr := a.filesBackend.WriteFile(reader, filePath) _, appErr := a.filesBackend.WriteFile(reader, filePath)

View File

@ -18,12 +18,12 @@ func TestGetSharing(t *testing.T) {
defer tearDown() defer tearDown()
container := st.Container{ container := st.Container{
WorkspaceID: utils.CreateGUID(), WorkspaceID: utils.NewID(utils.IDTypeWorkspace),
} }
t.Run("should get a sharing successfully", func(t *testing.T) { t.Run("should get a sharing successfully", func(t *testing.T) {
want := &model.Sharing{ want := &model.Sharing{
ID: utils.CreateGUID(), ID: utils.NewID(utils.IDTypeBlock),
Enabled: true, Enabled: true,
Token: "token", Token: "token",
ModifiedBy: "otherid", ModifiedBy: "otherid",
@ -67,10 +67,10 @@ func TestUpsertSharing(t *testing.T) {
defer tearDown() defer tearDown()
container := st.Container{ container := st.Container{
WorkspaceID: utils.CreateGUID(), WorkspaceID: utils.NewID(utils.IDTypeWorkspace),
} }
sharing := model.Sharing{ sharing := model.Sharing{
ID: utils.CreateGUID(), ID: utils.NewID(utils.IDTypeBlock),
Enabled: true, Enabled: true,
Token: "token", Token: "token",
ModifiedBy: "otherid", ModifiedBy: "otherid",

View File

@ -16,7 +16,7 @@ func (a *App) GetRootWorkspace() (*model.Workspace, error) {
if workspace == nil { if workspace == nil {
workspace = &model.Workspace{ workspace = &model.Workspace{
ID: workspaceID, ID: workspaceID,
SignupToken: utils.CreateGUID(), SignupToken: utils.NewID(utils.IDTypeToken),
} }
err := a.store.UpsertWorkspaceSignupToken(*workspace) err := a.store.UpsertWorkspaceSignupToken(*workspace)
if err != nil { if err != nil {

View File

@ -22,7 +22,7 @@ type TestHelper struct {
} }
var mockSession = &model.Session{ var mockSession = &model.Session{
ID: utils.CreateGUID(), ID: utils.NewID(utils.IDTypeSession),
Token: "goodToken", Token: "goodToken",
UserID: "12345", UserID: "12345",
CreateAt: time.Now().Unix() - 2000, CreateAt: time.Now().Unix() - 2000,

View File

@ -7,7 +7,6 @@ require (
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/golang-migrate/migrate/v4 v4.14.1 github.com/golang-migrate/migrate/v4 v4.14.1
github.com/golang/mock v1.5.0 github.com/golang/mock v1.5.0
github.com/google/uuid v1.2.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/lib/pq v1.10.2 github.com/lib/pq v1.10.2

View File

@ -17,8 +17,8 @@ func TestGetBlocks(t *testing.T) {
require.NoError(t, resp.Error) require.NoError(t, resp.Error)
initialCount := len(blocks) initialCount := len(blocks)
blockID1 := utils.CreateGUID() blockID1 := utils.NewID(utils.IDTypeBlock)
blockID2 := utils.CreateGUID() blockID2 := utils.NewID(utils.IDTypeBlock)
newBlocks := []model.Block{ newBlocks := []model.Block{
{ {
ID: blockID1, ID: blockID1,
@ -58,9 +58,9 @@ func TestPostBlock(t *testing.T) {
require.NoError(t, resp.Error) require.NoError(t, resp.Error)
initialCount := len(blocks) initialCount := len(blocks)
blockID1 := utils.CreateGUID() blockID1 := utils.NewID(utils.IDTypeBlock)
blockID2 := utils.CreateGUID() blockID2 := utils.NewID(utils.IDTypeBlock)
blockID3 := utils.CreateGUID() blockID3 := utils.NewID(utils.IDTypeBlock)
t.Run("Create a single block", func(t *testing.T) { t.Run("Create a single block", func(t *testing.T) {
block := model.Block{ block := model.Block{
@ -152,7 +152,7 @@ func TestPatchBlock(t *testing.T) {
th := SetupTestHelper().InitBasic() th := SetupTestHelper().InitBasic()
defer th.TearDown() defer th.TearDown()
blockID := utils.CreateGUID() blockID := utils.NewID(utils.IDTypeBlock)
block := model.Block{ block := model.Block{
ID: blockID, ID: blockID,
@ -253,7 +253,7 @@ func TestDeleteBlock(t *testing.T) {
require.NoError(t, resp.Error) require.NoError(t, resp.Error)
initialCount := len(blocks) initialCount := len(blocks)
blockID := utils.CreateGUID() blockID := utils.NewID(utils.IDTypeBlock)
t.Run("Create a block", func(t *testing.T) { t.Run("Create a block", func(t *testing.T) {
block := model.Block{ block := model.Block{
ID: blockID, ID: blockID,
@ -298,9 +298,10 @@ func TestGetSubtree(t *testing.T) {
require.NoError(t, resp.Error) require.NoError(t, resp.Error)
initialCount := len(blocks) initialCount := len(blocks)
parentBlockID := utils.CreateGUID() parentBlockID := utils.NewID(utils.IDTypeBlock)
childBlockID1 := utils.CreateGUID() childBlockID1 := utils.NewID(utils.IDTypeBlock)
childBlockID2 := utils.CreateGUID() childBlockID2 := utils.NewID(utils.IDTypeBlock)
t.Run("Create the block structure", func(t *testing.T) { t.Run("Create the block structure", func(t *testing.T) {
newBlocks := []model.Block{ newBlocks := []model.Block{
{ {

View File

@ -12,8 +12,8 @@ func TestSharing(t *testing.T) {
th := SetupTestHelper().InitBasic() th := SetupTestHelper().InitBasic()
defer th.TearDown() defer th.TearDown()
rootID := utils.CreateGUID() rootID := utils.NewID(utils.IDTypeBlock)
token := utils.CreateGUID() token := utils.NewID(utils.IDTypeToken)
t.Run("Check no initial sharing", func(t *testing.T) { t.Run("Check no initial sharing", func(t *testing.T) {
sharing, resp := th.Client.GetSharing(rootID) sharing, resp := th.Client.GetSharing(rootID)

View File

@ -23,7 +23,7 @@ func TestUserRegister(t *testing.T) {
registerRequest := &api.RegisterRequest{ registerRequest := &api.RegisterRequest{
Username: fakeUsername, Username: fakeUsername,
Email: fakeEmail, Email: fakeEmail,
Password: utils.CreateGUID(), Password: utils.NewID(utils.IDTypeNone),
} }
success, resp := th.Client.Register(registerRequest) success, resp := th.Client.Register(registerRequest)
require.NoError(t, resp.Error) require.NoError(t, resp.Error)
@ -44,7 +44,7 @@ func TestUserLogin(t *testing.T) {
Type: "normal", Type: "normal",
Username: "nonexistuser", Username: "nonexistuser",
Email: "", Email: "",
Password: utils.CreateGUID(), Password: utils.NewID(utils.IDTypeNone),
} }
data, resp := th.Client.Login(loginRequest) data, resp := th.Client.Login(loginRequest)
require.Error(t, resp.Error) require.Error(t, resp.Error)
@ -52,7 +52,7 @@ func TestUserLogin(t *testing.T) {
}) })
t.Run("with registered user", func(t *testing.T) { t.Run("with registered user", func(t *testing.T) {
password := utils.CreateGUID() password := utils.NewID(utils.IDTypeNone)
// register // register
registerRequest := &api.RegisterRequest{ registerRequest := &api.RegisterRequest{
Username: fakeUsername, Username: fakeUsername,
@ -89,7 +89,7 @@ func TestGetMe(t *testing.T) {
t.Run("logged in", func(t *testing.T) { t.Run("logged in", func(t *testing.T) {
// register // register
password := utils.CreateGUID() password := utils.NewID(utils.IDTypeNone)
registerRequest := &api.RegisterRequest{ registerRequest := &api.RegisterRequest{
Username: fakeUsername, Username: fakeUsername,
Email: fakeEmail, Email: fakeEmail,
@ -124,7 +124,7 @@ func TestGetUser(t *testing.T) {
defer th.TearDown() defer th.TearDown()
// register // register
password := utils.CreateGUID() password := utils.NewID(utils.IDTypeNone)
registerRequest := &api.RegisterRequest{ registerRequest := &api.RegisterRequest{
Username: fakeUsername, Username: fakeUsername,
Email: fakeEmail, Email: fakeEmail,
@ -169,7 +169,7 @@ func TestUserChangePassword(t *testing.T) {
defer th.TearDown() defer th.TearDown()
// register // register
password := utils.CreateGUID() password := utils.NewID(utils.IDTypeNone)
registerRequest := &api.RegisterRequest{ registerRequest := &api.RegisterRequest{
Username: fakeUsername, Username: fakeUsername,
Email: fakeEmail, Email: fakeEmail,
@ -197,7 +197,7 @@ func TestUserChangePassword(t *testing.T) {
// change password // change password
success, resp = th.Client.UserChangePassword(originalMe.ID, &api.ChangePasswordRequest{ success, resp = th.Client.UserChangePassword(originalMe.ID, &api.ChangePasswordRequest{
OldPassword: password, OldPassword: password,
NewPassword: utils.CreateGUID(), NewPassword: utils.NewID(utils.IDTypeNone),
}) })
require.NoError(t, resp.Error) require.NoError(t, resp.Error)
require.True(t, success) require.True(t, success)
@ -216,7 +216,7 @@ func TestWorkspaceUploadFile(t *testing.T) {
defer th.TearDown() defer th.TearDown()
workspaceID := "0" workspaceID := "0"
rootID := utils.CreateGUID() rootID := utils.NewID(utils.IDTypeBlock)
data := randomBytes(t, 1024) data := randomBytes(t, 1024)
result, resp := th.Client.WorkspaceUploadFile(workspaceID, rootID, bytes.NewReader(data)) result, resp := th.Client.WorkspaceUploadFile(workspaceID, rootID, bytes.NewReader(data))
require.Error(t, resp.Error) require.Error(t, resp.Error)
@ -228,7 +228,7 @@ func TestWorkspaceUploadFile(t *testing.T) {
defer th.TearDown() defer th.TearDown()
workspaceID := "0" workspaceID := "0"
rootID := utils.CreateGUID() rootID := utils.NewID(utils.IDTypeBlock)
data := randomBytes(t, 1024) data := randomBytes(t, 1024)
result, resp := th.Client.WorkspaceUploadFile(workspaceID, rootID, bytes.NewReader(data)) result, resp := th.Client.WorkspaceUploadFile(workspaceID, rootID, bytes.NewReader(data))
require.NoError(t, resp.Error) require.NoError(t, resp.Error)

View File

@ -11,7 +11,6 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/google/uuid"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -30,6 +29,7 @@ import (
"github.com/mattermost/focalboard/server/services/store/sqlstore" "github.com/mattermost/focalboard/server/services/store/sqlstore"
"github.com/mattermost/focalboard/server/services/telemetry" "github.com/mattermost/focalboard/server/services/telemetry"
"github.com/mattermost/focalboard/server/services/webhook" "github.com/mattermost/focalboard/server/services/webhook"
"github.com/mattermost/focalboard/server/utils"
"github.com/mattermost/focalboard/server/web" "github.com/mattermost/focalboard/server/web"
"github.com/mattermost/focalboard/server/ws" "github.com/mattermost/focalboard/server/ws"
"github.com/oklog/run" "github.com/oklog/run"
@ -37,7 +37,6 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/shared/filestore" "github.com/mattermost/mattermost-server/v6/shared/filestore"
"github.com/mattermost/mattermost-server/v6/utils"
) )
const ( const (
@ -170,8 +169,8 @@ func New(params Params) (*Server, error) {
// Init telemetry // Init telemetry
telemetryID := settings["TelemetryID"] telemetryID := settings["TelemetryID"]
if len(telemetryID) == 0 { if len(telemetryID) == 0 {
telemetryID = uuid.New().String() telemetryID = utils.NewID(utils.IDTypeNone)
if err = params.DBStore.SetSystemSetting("TelemetryID", uuid.New().String()); err != nil { if err = params.DBStore.SetSystemSetting("TelemetryID", telemetryID); err != nil {
return nil, err return nil, err
} }
} }
@ -284,7 +283,7 @@ func (s *Server) Start() error {
s.metricsUpdaterTask = scheduler.CreateRecurringTask("updateMetrics", metricsUpdater, updateMetricsTaskFrequency) s.metricsUpdaterTask = scheduler.CreateRecurringTask("updateMetrics", metricsUpdater, updateMetricsTaskFrequency)
if s.config.Telemetry { if s.config.Telemetry {
firstRun := utils.MillisFromTime(time.Now()) firstRun := utils.GetMillis()
s.telemetry.RunTelemetryJob(firstRun) s.telemetry.RunTelemetryJob(firstRun)
} }

View File

@ -51,7 +51,7 @@ func (s *SQLStore) UpsertWorkspaceSignupToken(workspace model.Workspace) error {
func (s *SQLStore) UpsertWorkspaceSettings(workspace model.Workspace) error { func (s *SQLStore) UpsertWorkspaceSettings(workspace model.Workspace) error {
now := time.Now().Unix() now := time.Now().Unix()
signupToken := utils.CreateGUID() signupToken := utils.NewID(utils.IDTypeToken)
settingsJSON, err := json.Marshal(workspace.Settings) settingsJSON, err := json.Marshal(workspace.Settings)
if err != nil { if err != nil {

View File

@ -8,11 +8,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/google/uuid"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/mattermost/focalboard/server/model" "github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/services/store" "github.com/mattermost/focalboard/server/services/store"
"github.com/mattermost/focalboard/server/utils"
) )
func StoreTestUserStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { func StoreTestUserStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {
@ -47,7 +47,7 @@ func testGetWorkspaceUsers(t *testing.T, store store.Store) {
require.Equal(t, 0, len(users)) require.Equal(t, 0, len(users))
require.Equal(t, sql.ErrNoRows, err) require.Equal(t, sql.ErrNoRows, err)
userID := uuid.New().String() userID := utils.NewID(utils.IDTypeUser)
err = store.CreateUser(&model.User{ err = store.CreateUser(&model.User{
ID: userID, ID: userID,
@ -71,7 +71,7 @@ func testGetWorkspaceUsers(t *testing.T, store store.Store) {
func testCreateAndGetUser(t *testing.T, store store.Store) { func testCreateAndGetUser(t *testing.T, store store.Store) {
user := &model.User{ user := &model.User{
ID: uuid.New().String(), ID: utils.NewID(utils.IDTypeUser),
Username: "damao", Username: "damao",
Email: "mock@email.com", Email: "mock@email.com",
} }
@ -108,7 +108,7 @@ func testCreateAndGetUser(t *testing.T, store store.Store) {
func testCreateAndUpdateUser(t *testing.T, store store.Store) { func testCreateAndUpdateUser(t *testing.T, store store.Store) {
user := &model.User{ user := &model.User{
ID: uuid.New().String(), ID: utils.NewID(utils.IDTypeUser),
} }
err := store.CreateUser(user) err := store.CreateUser(user)
require.NoError(t, err) require.NoError(t, err)
@ -129,7 +129,7 @@ func testCreateAndUpdateUser(t *testing.T, store store.Store) {
}) })
t.Run("UpdateUserPassword", func(t *testing.T) { t.Run("UpdateUserPassword", func(t *testing.T) {
newPassword := uuid.New().String() newPassword := utils.NewID(utils.IDTypeNone)
err := store.UpdateUserPassword(user.Username, newPassword) err := store.UpdateUserPassword(user.Username, newPassword)
require.NoError(t, err) require.NoError(t, err)
@ -140,7 +140,7 @@ func testCreateAndUpdateUser(t *testing.T, store store.Store) {
}) })
t.Run("UpdateUserPasswordByID", func(t *testing.T) { t.Run("UpdateUserPasswordByID", func(t *testing.T) {
newPassword := uuid.New().String() newPassword := utils.NewID(utils.IDTypeNone)
err := store.UpdateUserPasswordByID(user.ID, newPassword) err := store.UpdateUserPasswordByID(user.ID, newPassword)
require.NoError(t, err) require.NoError(t, err)
@ -155,7 +155,7 @@ func testCreateAndGetRegisteredUserCount(t *testing.T, store store.Store) {
randomN := int(time.Now().Unix() % 10) randomN := int(time.Now().Unix() % 10)
for i := 0; i < randomN; i++ { for i := 0; i < randomN; i++ {
err := store.CreateUser(&model.User{ err := store.CreateUser(&model.User{
ID: uuid.New().String(), ID: utils.NewID(utils.IDTypeUser),
}) })
require.NoError(t, err) require.NoError(t, err)
} }

View File

@ -42,7 +42,7 @@ func testUpsertWorkspaceSignupToken(t *testing.T, store store.Store) {
workspaceID := "0" workspaceID := "0"
workspace := &model.Workspace{ workspace := &model.Workspace{
ID: workspaceID, ID: workspaceID,
SignupToken: utils.CreateGUID(), SignupToken: utils.NewID(utils.IDTypeToken),
} }
// insert // insert
@ -55,7 +55,7 @@ func testUpsertWorkspaceSignupToken(t *testing.T, store store.Store) {
require.Equal(t, workspace.SignupToken, got.SignupToken) require.Equal(t, workspace.SignupToken, got.SignupToken)
// update signup token // update signup token
workspace.SignupToken = utils.CreateGUID() workspace.SignupToken = utils.NewID(utils.IDTypeToken)
err = store.UpsertWorkspaceSignupToken(*workspace) err = store.UpsertWorkspaceSignupToken(*workspace)
require.NoError(t, err) require.NoError(t, err)
@ -108,7 +108,7 @@ func testGetWorkspaceCount(t *testing.T, store store.Store) {
workspaceID := fmt.Sprintf("%d", i) workspaceID := fmt.Sprintf("%d", i)
workspace := &model.Workspace{ workspace := &model.Workspace{
ID: workspaceID, ID: workspaceID,
SignupToken: utils.CreateGUID(), SignupToken: utils.NewID(utils.IDTypeToken),
} }
err := store.UpsertWorkspaceSignupToken(*workspace) err := store.UpsertWorkspaceSignupToken(*workspace)

View File

@ -1,28 +1,52 @@
package utils package utils
import ( import (
"crypto/rand"
"encoding/json" "encoding/json"
"fmt"
"log"
"time" "time"
mm_model "github.com/mattermost/mattermost-server/v6/model"
) )
// CreateGUID returns a random GUID. type IDType byte
func CreateGUID() string {
b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
log.Fatal(err)
}
uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
return uuid const (
IDTypeNone IDType = '7'
IDTypeWorkspace IDType = 'w'
IDTypeBoard IDType = 'b'
IDTypeCard IDType = 'c'
IDTypeView IDType = 'v'
IDTypeSession IDType = 's'
IDTypeUser IDType = 'u'
IDTypeToken IDType = 'k'
IDTypeBlock IDType = 'a'
)
// NewId is a globally unique identifier. It is a [A-Z0-9] string 27
// characters long. It is a UUID version 4 Guid that is zbased32 encoded
// with the padding stripped off, and a one character alpha prefix indicating the
// type of entity or a `7` if unknown type.
func NewID(idType IDType) string {
return string(idType) + mm_model.NewId()
} }
// GetMillis is a convenience method to get milliseconds since epoch. // GetMillis is a convenience method to get milliseconds since epoch.
func GetMillis() int64 { func GetMillis() int64 {
return time.Now().UnixNano() / int64(time.Millisecond) return mm_model.GetMillis()
}
// GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time.
func GetMillisForTime(thisTime time.Time) int64 {
return mm_model.GetMillisForTime(thisTime)
}
// GetTimeForMillis is a convenience method to get time.Time for milliseconds since epoch.
func GetTimeForMillis(millis int64) time.Time {
return mm_model.GetTimeForMillis(millis)
}
// SecondsToMillis is a convenience method to convert seconds to milliseconds.
func SecondsToMillis(seconds int64) int64 {
return seconds * 1000
} }
func StructToMap(v interface{}) (m map[string]interface{}) { func StructToMap(v interface{}) (m map[string]interface{}) {

View File

@ -41,7 +41,7 @@ interface Block {
function createBlock(block?: Block): Block { function createBlock(block?: Block): Block {
const now = Date.now() const now = Date.now()
return { return {
id: block?.id || Utils.createGuid(), id: block?.id || Utils.createGuid(Utils.blockTypeToIDType(block?.type)),
schema: 1, schema: 1,
workspaceId: block?.workspaceId || '', workspaceId: block?.workspaceId || '',
parentId: block?.parentId || '', parentId: block?.parentId || '',

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Utils} from '../utils' import {Utils, IDType} from '../utils'
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../telemetry/telemetryClient' import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../telemetry/telemetryClient'
@ -40,7 +40,7 @@ function createBoard(block?: Block): Board {
const selectProperties = cardProperties.find((o) => o.type === 'select') const selectProperties = cardProperties.find((o) => o.type === 'select')
if (!selectProperties) { if (!selectProperties) {
const property: IPropertyTemplate = { const property: IPropertyTemplate = {
id: Utils.createGuid(), id: Utils.createGuid(IDType.BlockID),
name: 'Status', name: 'Status',
type: 'select', type: 'select',
options: [], options: [],

View File

@ -8,7 +8,7 @@ import {Board, IPropertyOption, IPropertyTemplate, BoardGroup} from '../../block
import {Card} from '../../blocks/card' import {Card} from '../../blocks/card'
import {BoardView} from '../../blocks/boardView' import {BoardView} from '../../blocks/boardView'
import mutator from '../../mutator' import mutator from '../../mutator'
import {Utils} from '../../utils' import {Utils, IDType} from '../../utils'
import Button from '../../widgets/buttons/button' import Button from '../../widgets/buttons/button'
import KanbanCard from './kanbanCard' import KanbanCard from './kanbanCard'
@ -55,7 +55,7 @@ const Kanban = (props: Props) => {
Utils.log('onAddGroupClicked') Utils.log('onAddGroupClicked')
const option: IPropertyOption = { const option: IPropertyOption = {
id: Utils.createGuid(), id: Utils.createGuid(IDType.BlockID),
value: 'New group', value: 'New group',
color: 'propColorDefault', color: 'propColorDefault',
} }

View File

@ -6,7 +6,7 @@ import {Editor} from 'codemirror'
import SimpleMDE from 'easymde' import SimpleMDE from 'easymde'
import 'easymde/dist/easymde.min.css' import 'easymde/dist/easymde.min.css'
import {Utils} from '../utils' import {Utils, IDType} from '../utils'
import './markdownEditor.scss' import './markdownEditor.scss'
type Props = { type Props = {
@ -25,7 +25,7 @@ type Props = {
const MarkdownEditor = (props: Props): JSX. Element => { const MarkdownEditor = (props: Props): JSX. Element => {
const {placeholderText, onFocus, onBlur, onChange, text, id} = props const {placeholderText, onFocus, onBlur, onChange, text, id} = props
const [isEditing, setIsEditing] = useState(false) const [isEditing, setIsEditing] = useState(false)
const [uniqueId] = useState(id || Utils.createGuid()) const [uniqueId] = useState(id || Utils.createGuid(IDType.None))
const [active, setActive] = useState(false) const [active, setActive] = useState(false)
const [editorInstance, setEditorInstance] = useState<SimpleMDE>() const [editorInstance, setEditorInstance] = useState<SimpleMDE>()

View File

@ -10,7 +10,7 @@ import {ContentBlock} from '../blocks/contentBlock'
import {CommentBlock} from '../blocks/commentBlock' import {CommentBlock} from '../blocks/commentBlock'
import mutator from '../mutator' import mutator from '../mutator'
import {OctoUtils} from '../octoUtils' import {OctoUtils} from '../octoUtils'
import {Utils} from '../utils' import {Utils, IDType} from '../utils'
import Editable from '../widgets/editable' import Editable from '../widgets/editable'
import Switch from '../widgets/switch' import Switch from '../widgets/switch'
@ -110,7 +110,7 @@ const PropertyValueElement = (props:Props): JSX.Element => {
onCreate={ onCreate={
async (newValue, currentValues) => { async (newValue, currentValues) => {
const option: IPropertyOption = { const option: IPropertyOption = {
id: Utils.createGuid(), id: Utils.createGuid(IDType.BlockID),
value: newValue, value: newValue,
color: 'propColorDefault', color: 'propColorDefault',
} }
@ -134,7 +134,7 @@ const PropertyValueElement = (props:Props): JSX.Element => {
onCreate={ onCreate={
async (newValue) => { async (newValue) => {
const option: IPropertyOption = { const option: IPropertyOption = {
id: Utils.createGuid(), id: Utils.createGuid(IDType.BlockID),
value: newValue, value: newValue,
color: 'propColorDefault', color: 'propColorDefault',
} }

View File

@ -8,7 +8,7 @@ import {ISharing} from '../blocks/sharing'
import client from '../octoClient' import client from '../octoClient'
import {Utils} from '../utils' import {Utils, IDType} from '../utils'
import {sendFlashMessage} from '../components/flashMessages' import {sendFlashMessage} from '../components/flashMessages'
import Button from '../widgets/buttons/button' import Button from '../widgets/buttons/button'
@ -38,7 +38,7 @@ const ShareBoardComponent = React.memo((props: Props): JSX.Element => {
const newSharing: ISharing = { const newSharing: ISharing = {
id: props.boardId, id: props.boardId,
enabled: true, enabled: true,
token: Utils.createGuid(), token: Utils.createGuid(IDType.Token),
} }
return newSharing return newSharing
} }
@ -56,7 +56,7 @@ const ShareBoardComponent = React.memo((props: Props): JSX.Element => {
const accept = window.confirm(intl.formatMessage({id: 'ShareBoard.confirmRegenerateToken', defaultMessage: 'This will invalidate previously shared links. Continue?'})) const accept = window.confirm(intl.formatMessage({id: 'ShareBoard.confirmRegenerateToken', defaultMessage: 'This will invalidate previously shared links. Continue?'}))
if (accept) { if (accept) {
const newSharing: ISharing = sharing || createSharingInfo() const newSharing: ISharing = sharing || createSharingInfo()
newSharing.token = Utils.createGuid() newSharing.token = Utils.createGuid(IDType.Token)
await client.setSharing(newSharing) await client.setSharing(newSharing)
await loadData() await loadData()

View File

@ -15,7 +15,7 @@ import {BoardView} from '../../blocks/boardView'
import {IUser} from '../../user' import {IUser} from '../../user'
import {Utils} from '../../utils' import {Utils, IDType} from '../../utils'
import {wrapDNDIntl} from '../../testUtils' import {wrapDNDIntl} from '../../testUtils'
@ -179,7 +179,7 @@ describe('components/table/Table extended', () => {
test('should match snapshot with CreatedBy', async () => { test('should match snapshot with CreatedBy', async () => {
const board = TestBlockFactory.createBoard() const board = TestBlockFactory.createBoard()
const dateCreatedId = Utils.createGuid() const dateCreatedId = Utils.createGuid(IDType.User)
board.fields.cardProperties.push({ board.fields.cardProperties.push({
id: dateCreatedId, id: dateCreatedId,
name: 'Date Created', name: 'Date Created',
@ -236,7 +236,7 @@ describe('components/table/Table extended', () => {
test('should match snapshot with UpdatedAt', async () => { test('should match snapshot with UpdatedAt', async () => {
const board = TestBlockFactory.createBoard() const board = TestBlockFactory.createBoard()
const dateUpdatedId = Utils.createGuid() const dateUpdatedId = Utils.createGuid(IDType.User)
board.fields.cardProperties.push({ board.fields.cardProperties.push({
id: dateUpdatedId, id: dateUpdatedId,
name: 'Date Updated', name: 'Date Updated',
@ -315,7 +315,7 @@ describe('components/table/Table extended', () => {
test('should match snapshot with CreatedBy', async () => { test('should match snapshot with CreatedBy', async () => {
const board = TestBlockFactory.createBoard() const board = TestBlockFactory.createBoard()
const createdById = Utils.createGuid() const createdById = Utils.createGuid(IDType.User)
board.fields.cardProperties.push({ board.fields.cardProperties.push({
id: createdById, id: createdById,
name: 'Created By', name: 'Created By',
@ -373,7 +373,7 @@ describe('components/table/Table extended', () => {
test('should match snapshot with UpdatedBy', async () => { test('should match snapshot with UpdatedBy', async () => {
const board = TestBlockFactory.createBoard() const board = TestBlockFactory.createBoard()
const modifiedById = Utils.createGuid() const modifiedById = Utils.createGuid(IDType.User)
board.fields.cardProperties.push({ board.fields.cardProperties.push({
id: modifiedById, id: modifiedById,
name: 'Last Modified By', name: 'Last Modified By',

View File

@ -8,7 +8,7 @@ import {Board, IPropertyTemplate} from '../blocks/board'
import {IViewType, BoardView, createBoardView} from '../blocks/boardView' import {IViewType, BoardView, createBoardView} from '../blocks/boardView'
import {Constants} from '../constants' import {Constants} from '../constants'
import mutator from '../mutator' import mutator from '../mutator'
import {Utils} from '../utils' import {Utils, IDType} from '../utils'
import AddIcon from '../widgets/icons/add' import AddIcon from '../widgets/icons/add'
import BoardIcon from '../widgets/icons/board' import BoardIcon from '../widgets/icons/board'
import DeleteIcon from '../widgets/icons/delete' import DeleteIcon from '../widgets/icons/delete'
@ -43,7 +43,7 @@ const ViewMenu = React.memo((props: Props) => {
const currentViewId = activeView.id const currentViewId = activeView.id
const newView = createBoardView(activeView) const newView = createBoardView(activeView)
newView.title = `${activeView.title} copy` newView.title = `${activeView.title} copy`
newView.id = Utils.createGuid() newView.id = Utils.createGuid(IDType.View)
mutator.insertBlock( mutator.insertBlock(
newView, newView,
'duplicate view', 'duplicate view',

View File

@ -9,7 +9,7 @@ import {FilterGroup} from './blocks/filterGroup'
import octoClient, {OctoClient} from './octoClient' import octoClient, {OctoClient} from './octoClient'
import {OctoUtils} from './octoUtils' import {OctoUtils} from './octoUtils'
import undoManager from './undomanager' import undoManager from './undomanager'
import {Utils} from './utils' import {Utils, IDType} from './utils'
import {UserSettings} from './userSettings' import {UserSettings} from './userSettings'
import TelemetryClient, {TelemetryCategory, TelemetryActions} from './telemetry/telemetryClient' import TelemetryClient, {TelemetryCategory, TelemetryActions} from './telemetry/telemetryClient'
@ -25,7 +25,7 @@ class Mutator {
Utils.assertFailure('UndoManager does not support nested groups') Utils.assertFailure('UndoManager does not support nested groups')
return undefined return undefined
} }
this.undoGroupId = Utils.createGuid() this.undoGroupId = Utils.createGuid(IDType.None)
return this.undoGroupId return this.undoGroupId
} }
@ -231,7 +231,7 @@ class Mutator {
} }
const newTemplate = template || { const newTemplate = template || {
id: Utils.createGuid(), id: Utils.createGuid(IDType.BlockID),
name: 'New Property', name: 'New Property',
type: 'text', type: 'text',
options: [], options: [],
@ -276,7 +276,7 @@ class Mutator {
} }
const srcTemplate = newBoard.fields.cardProperties[index] const srcTemplate = newBoard.fields.cardProperties[index]
const newTemplate: IPropertyTemplate = { const newTemplate: IPropertyTemplate = {
id: Utils.createGuid(), id: Utils.createGuid(IDType.BlockID),
name: `${srcTemplate.name} copy`, name: `${srcTemplate.name} copy`,
type: srcTemplate.type, type: srcTemplate.type,
options: srcTemplate.options.slice(), options: srcTemplate.options.slice(),
@ -462,7 +462,7 @@ class Mutator {
let option = newTemplate.options.find((o: IPropertyOption) => o.value === oldValue) let option = newTemplate.options.find((o: IPropertyOption) => o.value === oldValue)
if (!option) { if (!option) {
option = { option = {
id: Utils.createGuid(), id: Utils.createGuid(IDType.None),
value: oldValue, value: oldValue,
color: 'propColorDefault', color: 'propColorDefault',
} }

View File

@ -114,7 +114,7 @@ class OctoUtils {
const now = Date.now() const now = Date.now()
const newBlocks = blocks.map((block) => { const newBlocks = blocks.map((block) => {
const newBlock = this.hydrateBlock(block) const newBlock = this.hydrateBlock(block)
newBlock.id = Utils.createGuid() newBlock.id = Utils.createGuid(Utils.blockTypeToIDType(newBlock.type))
newBlock.createAt = now newBlock.createAt = now
newBlock.updateAt = now newBlock.updateAt = now
idMap[block.id] = newBlock.id idMap[block.id] = newBlock.id

View File

@ -3,7 +3,7 @@
import {createIntl} from 'react-intl' import {createIntl} from 'react-intl'
import {Utils} from './utils' import {Utils, IDType} from './utils'
describe('utils', () => { describe('utils', () => {
describe('assureProtocol', () => { describe('assureProtocol', () => {
@ -23,6 +23,21 @@ describe('utils', () => {
}) })
}) })
describe('createGuid', () => {
test('should create 27 char random id for workspace', () => {
expect(Utils.createGuid(IDType.Workspace)).toMatch(/^w[ybndrfg8ejkmcpqxot1uwisza345h769]{26}$/)
})
test('should create 27 char random id for board', () => {
expect(Utils.createGuid(IDType.Board)).toMatch(/^b[ybndrfg8ejkmcpqxot1uwisza345h769]{26}$/)
})
test('should create 27 char random id for card', () => {
expect(Utils.createGuid(IDType.Card)).toMatch(/^c[ybndrfg8ejkmcpqxot1uwisza345h769]{26}$/)
})
test('should create 27 char random id', () => {
expect(Utils.createGuid(IDType.None)).toMatch(/^7[ybndrfg8ejkmcpqxot1uwisza345h769]{26}$/)
})
})
describe('htmlFromMarkdown', () => { describe('htmlFromMarkdown', () => {
test('should not allow XSS on links href on the webapp', () => { test('should not allow XSS on links href on the webapp', () => {
expect(Utils.htmlFromMarkdown('[]("xss-attack="true"other="whatever)')).toBe('<p><a target="_blank" rel="noreferrer" href="%22xss-attack=%22true%22other=%22whatever" title="" onclick="event.stopPropagation();"></a></p>') expect(Utils.htmlFromMarkdown('[]("xss-attack="true"other="whatever)')).toBe('<p><a target="_blank" rel="noreferrer" href="%22xss-attack=%22true%22other=%22whatever" title="" onclick="event.stopPropagation();"></a></p>')

View File

@ -19,20 +19,84 @@ const IconClass = 'octo-icon'
const OpenButtonClass = 'open-button' const OpenButtonClass = 'open-button'
const SpacerClass = 'octo-spacer' const SpacerClass = 'octo-spacer'
const HorizontalGripClass = 'HorizontalGrip' const HorizontalGripClass = 'HorizontalGrip'
const base32Alphabet = 'ybndrfg8ejkmcpqxot1uwisza345h769'
// eslint-disable-next-line no-shadow
enum IDType {
None = '7',
Workspace = 'w',
Board = 'b',
Card = 'c',
View = 'v',
Session = 's',
User = 'u',
Token = 'k',
BlockID = 'a',
}
class Utils { class Utils {
static createGuid(): string { static createGuid(idType: IDType): string {
const crypto = window.crypto || window.msCrypto const data = Utils.randomArray(16)
function randomDigit() { return idType + this.base32encode(data, false)
if (crypto && crypto.getRandomValues) { }
const rands = new Uint8Array(1)
crypto.getRandomValues(rands)
return (rands[0] % 16).toString(16)
}
return (Math.floor((Math.random() * 16))).toString(16) static blockTypeToIDType(blockType: string | undefined): IDType {
let ret: IDType = IDType.None
switch (blockType) {
case 'workspace':
ret = IDType.Workspace
break
case 'board':
ret = IDType.Board
break
case 'card':
ret = IDType.Card
break
case 'view':
ret = IDType.View
break
} }
return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit) return ret
}
static randomArray(size: number): Uint8Array {
const crypto = window.crypto || window.msCrypto
const rands = new Uint8Array(size)
if (crypto && crypto.getRandomValues) {
crypto.getRandomValues(rands)
} else {
for (let i = 0; i < size; i++) {
rands[i] = Math.floor((Math.random() * 255))
}
}
return rands
}
static base32encode(data: Int8Array | Uint8Array | Uint8ClampedArray, pad: boolean): string {
const dview = new DataView(data.buffer, data.byteOffset, data.byteLength)
let bits = 0
let value = 0
let output = ''
// adapted from https://github.com/LinusU/base32-encode
for (let i = 0; i < dview.byteLength; i++) {
value = (value << 8) | dview.getUint8(i)
bits += 8
while (bits >= 5) {
output += base32Alphabet[(value >>> (bits - 5)) & 31]
bits -= 5
}
}
if (bits > 0) {
output += base32Alphabet[(value << (5 - bits)) & 31]
}
if (pad) {
while ((output.length % 8) !== 0) {
output += '='
}
}
return output
} }
static htmlToElement(html: string): HTMLElement { static htmlToElement(html: string): HTMLElement {
@ -512,4 +576,4 @@ class Utils {
} }
} }
export {Utils} export {Utils, IDType}