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
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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)
defer a.audit.LogRecord(audit.LevelModify, auditRec)
workspace.SignupToken = utils.CreateGUID()
workspace.SignupToken = utils.NewID(utils.IDTypeToken)
err = a.app.UpsertWorkspaceSignupToken(*workspace)
if err != nil {

View file

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

View file

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

View file

@ -20,7 +20,7 @@ func (a *App) SaveFile(reader io.Reader, workspaceID, rootID, filename string) (
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)
_, appErr := a.filesBackend.WriteFile(reader, filePath)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,28 +1,52 @@
package utils
import (
"crypto/rand"
"encoding/json"
"fmt"
"log"
"time"
mm_model "github.com/mattermost/mattermost-server/v6/model"
)
// CreateGUID returns a random GUID.
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:])
type IDType byte
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.
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{}) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ import {ISharing} from '../blocks/sharing'
import client from '../octoClient'
import {Utils} from '../utils'
import {Utils, IDType} from '../utils'
import {sendFlashMessage} from '../components/flashMessages'
import Button from '../widgets/buttons/button'
@ -38,7 +38,7 @@ const ShareBoardComponent = React.memo((props: Props): JSX.Element => {
const newSharing: ISharing = {
id: props.boardId,
enabled: true,
token: Utils.createGuid(),
token: Utils.createGuid(IDType.Token),
}
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?'}))
if (accept) {
const newSharing: ISharing = sharing || createSharingInfo()
newSharing.token = Utils.createGuid()
newSharing.token = Utils.createGuid(IDType.Token)
await client.setSharing(newSharing)
await loadData()

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@
import {createIntl} from 'react-intl'
import {Utils} from './utils'
import {Utils, IDType} from './utils'
describe('utils', () => {
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', () => {
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>')

View file

@ -19,20 +19,84 @@ const IconClass = 'octo-icon'
const OpenButtonClass = 'open-button'
const SpacerClass = 'octo-spacer'
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 {
static createGuid(): string {
const crypto = window.crypto || window.msCrypto
function randomDigit() {
if (crypto && crypto.getRandomValues) {
const rands = new Uint8Array(1)
crypto.getRandomValues(rands)
return (rands[0] % 16).toString(16)
}
static createGuid(idType: IDType): string {
const data = Utils.randomArray(16)
return idType + this.base32encode(data, false)
}
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 {
@ -512,4 +576,4 @@ class Utils {
}
}
export {Utils}
export {Utils, IDType}