move users service to app directory (#18257)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
1934083291
Коммит
b66fd1f89e
288
app/users/bot_default_image.go
Обычный файл
288
app/users/bot_default_image.go
Обычный файл
@@ -0,0 +1,288 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
var botDefaultImage = []byte{
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7e,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0xec, 0xa6, 0x19, 0xa2, 0x00, 0x00, 0x00,
|
||||
0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61,
|
||||
0x05, 0x00, 0x00, 0x0c, 0xe3, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xed,
|
||||
0x5d, 0x5b, 0x88, 0x15, 0xc9, 0x19, 0xae, 0xf1, 0xba, 0x5e, 0xc6, 0x4b,
|
||||
0xbc, 0xa0, 0x46, 0xd7, 0x2b, 0xce, 0x2a, 0xba, 0xca, 0x8a, 0xa3, 0x82,
|
||||
0x38, 0xa3, 0x46, 0x47, 0x5d, 0x59, 0xa3, 0x2f, 0x9b, 0x28, 0x6e, 0xc2,
|
||||
0xea, 0x83, 0x22, 0x04, 0x82, 0x78, 0x41, 0xf2, 0xb2, 0x04, 0xd4, 0x07,
|
||||
0x49, 0x22, 0xc4, 0x07, 0xcd, 0x53, 0x30, 0x26, 0x1a, 0xa2, 0x08, 0xee,
|
||||
0xaa, 0xa8, 0x71, 0xb2, 0x41, 0x11, 0xbc, 0xdf, 0xd0, 0x8d, 0x6e, 0x24,
|
||||
0x82, 0xe3, 0x65, 0x32, 0x9b, 0x19, 0x1d, 0x47, 0xd7, 0xeb, 0xc9, 0xf7,
|
||||
0xb5, 0xa7, 0x0f, 0xe7, 0x9c, 0xe9, 0x3e, 0xdd, 0xe7, 0x4c, 0x57, 0x77,
|
||||
0x75, 0x75, 0xfd, 0xf0, 0x9f, 0xee, 0xae, 0xaa, 0xae, 0xfa, 0xff, 0xef,
|
||||
0xfb, 0x4f, 0x9d, 0xee, 0xaa, 0xea, 0x3e, 0x65, 0x42, 0x4f, 0xe9, 0x0b,
|
||||
0xb7, 0x46, 0x43, 0x2b, 0xd2, 0xdb, 0x91, 0xd8, 0xf6, 0x84, 0x96, 0x67,
|
||||
0x69, 0xf7, 0xf4, 0x3e, 0x36, 0xa2, 0x19, 0xfa, 0x34, 0xbd, 0xe5, 0x3e,
|
||||
0xf5, 0x31, 0xf4, 0xdf, 0xd0, 0x5b, 0xd0, 0x7f, 0xa5, 0xb7, 0x0d, 0xd8,
|
||||
0x6a, 0x25, 0x65, 0x1a, 0x78, 0xd3, 0x1b, 0x3e, 0x54, 0x41, 0x67, 0x42,
|
||||
0x2b, 0xa1, 0x24, 0x9d, 0x69, 0x32, 0xa4, 0x11, 0x95, 0x32, 0x18, 0xce,
|
||||
0x42, 0x6b, 0xa1, 0x5f, 0x43, 0x99, 0x16, 0x5b, 0x89, 0x63, 0x00, 0x74,
|
||||
0x03, 0xda, 0xb3, 0xa0, 0x24, 0xbc, 0x1a, 0x3a, 0x01, 0xda, 0x0e, 0x1a,
|
||||
0x85, 0xbc, 0x45, 0xa3, 0x57, 0xa0, 0xff, 0x80, 0x32, 0x20, 0x4e, 0x42,
|
||||
0x5b, 0xa0, 0x46, 0x02, 0x46, 0xa0, 0x3d, 0xea, 0xab, 0x81, 0xee, 0x86,
|
||||
0xb2, 0xab, 0x4e, 0x29, 0xaa, 0xb4, 0x8d, 0x36, 0xd2, 0x56, 0xda, 0x6c,
|
||||
0xa4, 0x8d, 0x08, 0x7c, 0x84, 0xf3, 0x7f, 0x03, 0x7d, 0x00, 0x55, 0x95,
|
||||
0x74, 0x37, 0xbb, 0x68, 0x33, 0x6d, 0xa7, 0x0f, 0x46, 0x8a, 0x44, 0xe0,
|
||||
0x63, 0x94, 0x3f, 0x0d, 0x75, 0x03, 0x37, 0x6e, 0xe9, 0xf4, 0x85, 0x3e,
|
||||
0x19, 0x29, 0x80, 0x00, 0xbb, 0xcc, 0x9f, 0x40, 0xf9, 0x9b, 0x1a, 0x37,
|
||||
0x82, 0xfd, 0xda, 0x4b, 0xdf, 0xe8, 0xa3, 0xf9, 0x79, 0x00, 0x08, 0xb6,
|
||||
0xf0, 0x02, 0xee, 0x73, 0xe8, 0xb7, 0x50, 0xbf, 0x40, 0xc6, 0xbd, 0x1c,
|
||||
0x7d, 0xa5, 0xcf, 0x51, 0x5d, 0xbc, 0xa2, 0x69, 0x35, 0x84, 0xb7, 0x6d,
|
||||
0xe7, 0xa0, 0x71, 0x27, 0xb4, 0x54, 0xfb, 0xe9, 0x3b, 0x31, 0x48, 0x9c,
|
||||
0x70, 0xa0, 0x66, 0x17, 0x94, 0xb7, 0x51, 0xa5, 0x82, 0xa7, 0xcb, 0x79,
|
||||
0xc4, 0x80, 0x58, 0x10, 0x13, 0xed, 0x85, 0xe3, 0x0e, 0xab, 0xa0, 0xdf,
|
||||
0x41, 0x75, 0x21, 0x30, 0x28, 0x3f, 0x88, 0x09, 0xb1, 0x89, 0xe3, 0xd8,
|
||||
0x0c, 0xcc, 0xf6, 0x16, 0x46, 0xf8, 0x57, 0xd0, 0xa0, 0x00, 0xd3, 0xb5,
|
||||
0x1e, 0x62, 0xa4, 0x5d, 0x6f, 0x50, 0x0d, 0xa7, 0xea, 0x0c, 0xf9, 0xbe,
|
||||
0x83, 0x9f, 0x58, 0x11, 0xb3, 0xd8, 0x0b, 0x6f, 0x77, 0xbe, 0x80, 0xbe,
|
||||
0x81, 0xea, 0xfa, 0x8d, 0x95, 0xe5, 0x17, 0x31, 0x23, 0x76, 0xb1, 0xbd,
|
||||
0x65, 0xe4, 0x84, 0x4c, 0xad, 0x21, 0xbe, 0xcd, 0x81, 0x4f, 0x0c, 0x89,
|
||||
0x65, 0xac, 0xe4, 0x7d, 0x58, 0x7b, 0x03, 0x2a, 0xeb, 0xdb, 0x91, 0xb4,
|
||||
0x7a, 0x89, 0x25, 0x31, 0x8d, 0x85, 0x8c, 0x87, 0x95, 0xf7, 0xa0, 0x49,
|
||||
0x23, 0x49, 0xb6, 0xbf, 0xc4, 0x94, 0xd8, 0x2a, 0x2d, 0xd5, 0xb0, 0xae,
|
||||
0x09, 0x2a, 0x1b, 0x8c, 0xa4, 0xd6, 0x4f, 0x6c, 0x89, 0xb1, 0x92, 0xf2,
|
||||
0x09, 0xac, 0xfa, 0x1e, 0x9a, 0x54, 0x72, 0xc2, 0xf2, 0x9b, 0x18, 0x13,
|
||||
0x6b, 0xa5, 0xa4, 0x1a, 0xd6, 0x18, 0xf2, 0xc3, 0x0b, 0x7e, 0x62, 0x4d,
|
||||
0xcc, 0x95, 0x90, 0x49, 0xb0, 0xe2, 0x09, 0x34, 0xac, 0x6f, 0x80, 0x69,
|
||||
0xe7, 0x1d, 0xd6, 0xc4, 0x9c, 0xd8, 0x47, 0x2a, 0x5c, 0x78, 0x59, 0x0f,
|
||||
0x35, 0xa4, 0x44, 0x83, 0x01, 0xb1, 0x27, 0x07, 0x91, 0xc8, 0x60, 0xb4,
|
||||
0x7a, 0x17, 0x6a, 0xc8, 0x8f, 0x16, 0x03, 0x72, 0x40, 0x2e, 0x42, 0x95,
|
||||
0x4e, 0x68, 0x2d, 0xc9, 0xd3, 0xb8, 0xaa, 0x05, 0x3d, 0xb9, 0x20, 0x27,
|
||||
0x45, 0x4b, 0xa9, 0xc3, 0x8c, 0xdb, 0xd1, 0xd2, 0x8f, 0x8b, 0x6e, 0xcd,
|
||||
0x9c, 0x20, 0x0b, 0x81, 0x41, 0xa8, 0xf8, 0x07, 0xd0, 0xc3, 0xb2, 0x1a,
|
||||
0xc8, 0xae, 0xf7, 0x53, 0x1c, 0xa8, 0xf6, 0x0d, 0x30, 0xf6, 0xbc, 0xe3,
|
||||
0x84, 0xdc, 0x48, 0x15, 0x5e, 0x70, 0x98, 0x2b, 0x7e, 0x75, 0xbf, 0x00,
|
||||
0xe4, 0xa6, 0xa8, 0x8b, 0xc2, 0x62, 0xd6, 0xa4, 0xbd, 0x87, 0xca, 0xff,
|
||||
0x06, 0x2d, 0x87, 0x1a, 0x51, 0x13, 0x01, 0x72, 0x43, 0x8e, 0xc8, 0x95,
|
||||
0x2f, 0x29, 0x26, 0x00, 0x7e, 0x85, 0x1a, 0x95, 0x1f, 0x8b, 0xf6, 0xe3,
|
||||
0xf5, 0x80, 0x01, 0x03, 0xc4, 0xee, 0xdd, 0xbb, 0x45, 0x5d, 0x5d, 0x9d,
|
||||
0xa5, 0xdc, 0x67, 0x9a, 0x26, 0x42, 0x8e, 0xc8, 0x55, 0xa0, 0xf2, 0x01,
|
||||
0x6a, 0x7b, 0x01, 0x8d, 0xfd, 0x6f, 0x6d, 0xbf, 0x7e, 0xfd, 0x52, 0x0f,
|
||||
0x1f, 0x3e, 0x4c, 0xe5, 0x0b, 0xd3, 0x98, 0xa7, 0x83, 0x8f, 0x69, 0xae,
|
||||
0xc8, 0x59, 0x60, 0x52, 0x8b, 0x9a, 0xb4, 0x00, 0x67, 0xd7, 0xae, 0x5d,
|
||||
0xf9, 0xdc, 0x67, 0x8e, 0x99, 0xa7, 0x8b, 0x9f, 0xf0, 0x83, 0x9c, 0x05,
|
||||
0x22, 0x9f, 0xa1, 0x16, 0x6d, 0x80, 0xb9, 0x7d, 0xfb, 0x76, 0x86, 0xf0,
|
||||
0xfc, 0x1d, 0xe6, 0xe9, 0xe4, 0x2b, 0x7c, 0x21, 0x77, 0x05, 0xa5, 0xac,
|
||||
0x60, 0xee, 0xbb, 0x95, 0x28, 0x7c, 0x1c, 0xba, 0x9f, 0x47, 0xb9, 0xd8,
|
||||
0x64, 0xbf, 0x78, 0xf1, 0x42, 0x74, 0xea, 0xe4, 0x3c, 0x66, 0xf2, 0xf2,
|
||||
0xe5, 0x4b, 0xd1, 0xb9, 0x73, 0xe7, 0xd8, 0xf8, 0xe2, 0xc3, 0xd0, 0xff,
|
||||
0xa2, 0x4c, 0x05, 0xd4, 0xf5, 0x11, 0x76, 0xaf, 0x8b, 0xc0, 0x8d, 0x38,
|
||||
0x59, 0x1b, 0xf2, 0x7d, 0x00, 0xa6, 0x5b, 0x11, 0x72, 0x47, 0x0e, 0x5d,
|
||||
0xa5, 0x50, 0x0f, 0xc0, 0x91, 0x25, 0x8e, 0x33, 0xf3, 0x4d, 0x1a, 0xda,
|
||||
0x48, 0xc2, 0x7a, 0x00, 0xf2, 0xf6, 0x14, 0x3a, 0x14, 0xfa, 0x3f, 0x1e,
|
||||
0xe4, 0x4b, 0xa1, 0x1e, 0xe0, 0x97, 0x28, 0xac, 0x15, 0xf9, 0xf9, 0xce,
|
||||
0x27, 0xe4, 0x98, 0x1c, 0x92, 0x4b, 0x47, 0x71, 0xeb, 0x01, 0x7a, 0xa1,
|
||||
0xf4, 0x7f, 0xa0, 0x3d, 0x1d, 0xcf, 0x8a, 0x71, 0x62, 0x02, 0x7b, 0x00,
|
||||
0xb2, 0xf5, 0x18, 0x3a, 0x0c, 0xca, 0x25, 0x65, 0x39, 0xe2, 0xd6, 0x03,
|
||||
0xfc, 0x02, 0xa5, 0xb4, 0x23, 0x3f, 0xc7, 0xf3, 0x64, 0x1d, 0x90, 0x4b,
|
||||
0x72, 0xda, 0x4a, 0x9c, 0x7a, 0x80, 0x2e, 0x28, 0xc5, 0x15, 0xa8, 0xbc,
|
||||
0x06, 0xd0, 0x4e, 0x12, 0xda, 0x03, 0x90, 0x47, 0x5e, 0x03, 0x70, 0xdd,
|
||||
0xc0, 0x73, 0x1e, 0xd8, 0xe2, 0xd4, 0x03, 0x70, 0x9a, 0x57, 0x4b, 0xf2,
|
||||
0x6d, 0xa7, 0x13, 0xba, 0x25, 0xa7, 0xad, 0xa6, 0xf0, 0x3b, 0x38, 0x80,
|
||||
0xf1, 0x73, 0x87, 0xb4, 0xc8, 0x93, 0x78, 0x7f, 0x3e, 0x67, 0xce, 0x1c,
|
||||
0x31, 0x7d, 0xfa, 0x74, 0x31, 0x68, 0xd0, 0x20, 0xd1, 0xbb, 0x77, 0xef,
|
||||
0x92, 0x6c, 0xea, 0xd8, 0xb1, 0xa3, 0xeb, 0x79, 0xcc, 0x3b, 0x74, 0xe8,
|
||||
0x90, 0x6b, 0x7e, 0xa1, 0x8c, 0xc6, 0xc6, 0x46, 0x6b, 0x5e, 0xe1, 0xf4,
|
||||
0xe9, 0xd3, 0xe2, 0xf8, 0xf1, 0xe3, 0x82, 0x3d, 0x8d, 0x82, 0xf2, 0x33,
|
||||
0xd8, 0xf4, 0x97, 0x42, 0x76, 0xfd, 0x10, 0x99, 0x4a, 0x3d, 0xc7, 0xd7,
|
||||
0xa5, 0x4b, 0x97, 0xd4, 0xa6, 0x4d, 0x9b, 0x52, 0x4d, 0x4d, 0x4d, 0xf9,
|
||||
0x03, 0x77, 0xca, 0x1e, 0xd3, 0x56, 0xda, 0x4c, 0xdb, 0x81, 0xa7, 0x4a,
|
||||
0x4a, 0x6e, 0xc9, 0xb1, 0xab, 0x6c, 0x40, 0x8e, 0x32, 0x06, 0x8f, 0x18,
|
||||
0x31, 0x22, 0x75, 0xe3, 0xc6, 0x0d, 0x65, 0x89, 0xf6, 0x32, 0x8c, 0xb6,
|
||||
0xd3, 0x07, 0x95, 0x30, 0x85, 0x2d, 0xe4, 0xd8, 0x55, 0x94, 0x79, 0x9e,
|
||||
0x6f, 0xd4, 0xa8, 0x51, 0xa9, 0xfa, 0xfa, 0x7a, 0x2f, 0x8c, 0x95, 0xcf,
|
||||
0xa7, 0x0f, 0xf4, 0x05, 0x88, 0xab, 0xa2, 0xe4, 0xd8, 0x51, 0x3e, 0x44,
|
||||
0xaa, 0x12, 0x46, 0x76, 0xed, 0xda, 0x35, 0x75, 0xed, 0xda, 0x35, 0xe5,
|
||||
0xc9, 0xf5, 0x6b, 0x20, 0x7d, 0xa1, 0x4f, 0xaa, 0xe0, 0x0b, 0x3b, 0xc8,
|
||||
0xb5, 0x25, 0xd9, 0x77, 0x01, 0x3f, 0xb2, 0x13, 0xa3, 0xde, 0xae, 0x5d,
|
||||
0xbb, 0x56, 0x8c, 0x1b, 0x37, 0x2e, 0x6a, 0x33, 0x02, 0x6b, 0x9f, 0xbe,
|
||||
0xd0, 0x27, 0x85, 0xc4, 0x91, 0x6b, 0xbe, 0x9a, 0x24, 0xf2, 0x28, 0x2d,
|
||||
0x2f, 0x2f, 0x8f, 0xd5, 0x05, 0x9f, 0xdf, 0x5e, 0x80, 0x17, 0x86, 0xf4,
|
||||
0x4d, 0x05, 0x8c, 0x61, 0xc3, 0x97, 0x76, 0x30, 0xda, 0x3d, 0x00, 0x6f,
|
||||
0x07, 0x67, 0xd8, 0x89, 0x51, 0x6e, 0x6b, 0x6a, 0x6a, 0x44, 0xcf, 0x9e,
|
||||
0xfa, 0x0d, 0x42, 0xd2, 0x27, 0xfa, 0xa6, 0x88, 0x54, 0xc1, 0x0e, 0x6b,
|
||||
0x08, 0xc0, 0x0e, 0x80, 0x4a, 0x24, 0x28, 0x31, 0xf1, 0x33, 0x77, 0xee,
|
||||
0x5c, 0x45, 0x30, 0x0a, 0xde, 0x0c, 0x85, 0x7c, 0x23, 0xd7, 0xe4, 0x3c,
|
||||
0xf3, 0xa6, 0xca, 0xd9, 0xc1, 0xbb, 0x5b, 0x5a, 0x8d, 0x43, 0x87, 0x0e,
|
||||
0x2d, 0xed, 0xc4, 0x18, 0x9c, 0xa5, 0x98, 0x6f, 0x16, 0xe7, 0x76, 0x0f,
|
||||
0x30, 0x5d, 0x15, 0xfc, 0xfa, 0xf7, 0xef, 0xaf, 0x8a, 0x29, 0x81, 0xdb,
|
||||
0xa1, 0x98, 0x6f, 0x16, 0xe7, 0x76, 0x00, 0x8c, 0x09, 0xdc, 0xdb, 0x12,
|
||||
0x2b, 0xec, 0xd0, 0xc1, 0xfa, 0x69, 0x2a, 0xf1, 0x6c, 0xb5, 0x4f, 0x53,
|
||||
0xcc, 0x37, 0x8b, 0x73, 0x06, 0x40, 0x37, 0x28, 0x67, 0x89, 0x8c, 0x24,
|
||||
0x0b, 0x01, 0x72, 0xde, 0x8d, 0x01, 0xc0, 0x45, 0x83, 0x4e, 0xd3, 0xc2,
|
||||
0xc9, 0x82, 0x23, 0x79, 0xde, 0x92, 0xf3, 0x0a, 0x3b, 0x00, 0x92, 0xe7,
|
||||
0xbe, 0xf1, 0x98, 0x08, 0x58, 0x01, 0x10, 0xe8, 0x13, 0x24, 0x51, 0xe0,
|
||||
0x7a, 0xe2, 0xc4, 0x09, 0xb1, 0x62, 0xc5, 0x0a, 0x31, 0x71, 0xe2, 0x44,
|
||||
0x31, 0x73, 0xe6, 0x4c, 0xb1, 0x7e, 0xfd, 0x7a, 0x71, 0xff, 0xfe, 0xfd,
|
||||
0xc0, 0x4c, 0x39, 0x7b, 0xf6, 0xac, 0x58, 0xb9, 0x72, 0xa5, 0x98, 0x30,
|
||||
0x61, 0x82, 0x98, 0x3f, 0x7f, 0xbe, 0xd8, 0xb6, 0x6d, 0x9b, 0x78, 0xf5,
|
||||
0xea, 0x55, 0x60, 0xf5, 0x47, 0x58, 0x91, 0xc5, 0xfd, 0x9f, 0x61, 0x40,
|
||||
0x28, 0x23, 0x54, 0x3d, 0x7a, 0xf4, 0xb0, 0x66, 0xc7, 0x38, 0x43, 0x46,
|
||||
0x1d, 0x3c, 0x78, 0x70, 0xab, 0x76, 0x8b, 0x99, 0x03, 0x78, 0xfb, 0xf6,
|
||||
0x6d, 0x6a, 0xf5, 0xea, 0xd5, 0xad, 0xea, 0xa0, 0x3f, 0x58, 0x2f, 0x90,
|
||||
0x3a, 0x78, 0xf0, 0xa0, 0xdf, 0x81, 0x3a, 0xd7, 0x72, 0x3b, 0x76, 0xec,
|
||||
0x48, 0x61, 0x9d, 0x40, 0xab, 0x36, 0x2a, 0x2b, 0x2b, 0x8b, 0x9e, 0xac,
|
||||
0xa2, 0x6f, 0xf9, 0x58, 0x13, 0x03, 0x1b, 0x0f, 0x6e, 0x89, 0x51, 0x7e,
|
||||
0x19, 0x89, 0xc7, 0xe4, 0x5e, 0x1c, 0x91, 0xd8, 0x80, 0xe5, 0xcc, 0xe4,
|
||||
0xc9, 0x93, 0x53, 0x17, 0x2e, 0x5c, 0x48, 0x91, 0xb0, 0x6c, 0x71, 0x02,
|
||||
0xa4, 0x98, 0x00, 0xd8, 0xbe, 0x7d, 0x7b, 0x41, 0xb0, 0xba, 0x77, 0xef,
|
||||
0x9e, 0xba, 0x73, 0xe7, 0x4e, 0x76, 0x93, 0x45, 0xed, 0x5f, 0xb9, 0x72,
|
||||
0x25, 0x85, 0x2b, 0x77, 0xd7, 0x36, 0x96, 0x2d, 0x5b, 0x56, 0x54, 0x7d,
|
||||
0x7e, 0xfc, 0x25, 0x46, 0xc4, 0x8a, 0x98, 0xc9, 0xe6, 0x25, 0xcd, 0xbd,
|
||||
0x38, 0x25, 0xb3, 0xa1, 0xb1, 0x63, 0xc7, 0xa6, 0xb0, 0x3a, 0xc6, 0x11,
|
||||
0x28, 0x3f, 0x80, 0x38, 0x9e, 0x88, 0xc4, 0xe7, 0xcf, 0x9f, 0xfb, 0x9a,
|
||||
0x61, 0x5b, 0xba, 0x74, 0xa9, 0x5b, 0x15, 0x9e, 0xe9, 0x4b, 0x96, 0x2c,
|
||||
0xf1, 0x24, 0xe1, 0xe6, 0xcd, 0x9b, 0x9e, 0xf5, 0xd8, 0x05, 0x8a, 0xf1,
|
||||
0x97, 0x98, 0x11, 0x3b, 0x99, 0xdc, 0xa0, 0xee, 0x53, 0xbc, 0x08, 0xec,
|
||||
0x01, 0x95, 0x26, 0x5b, 0xb6, 0x6c, 0x71, 0x7d, 0x14, 0xab, 0x2d, 0x8d,
|
||||
0xe2, 0xdb, 0x29, 0x9e, 0x3d, 0x7b, 0xe6, 0x59, 0xc5, 0x99, 0x33, 0x67,
|
||||
0x3c, 0xcb, 0xb8, 0x15, 0x38, 0x77, 0x8e, 0xaf, 0xde, 0x29, 0x2c, 0xe7,
|
||||
0xcf, 0x9f, 0x2f, 0x5c, 0xa0, 0xc4, 0x5c, 0x3e, 0xbe, 0x46, 0xec, 0x24,
|
||||
0x4b, 0x0f, 0x06, 0x80, 0xd4, 0x17, 0x3e, 0x4c, 0x9d, 0x3a, 0x55, 0x8a,
|
||||
0x0f, 0x77, 0xef, 0xde, 0xf5, 0x55, 0xef, 0xbd, 0x7b, 0xf7, 0x04, 0xba,
|
||||
0x55, 0x5f, 0x65, 0xb3, 0x0b, 0xf1, 0x9c, 0x07, 0x0f, 0x1e, 0x64, 0x27,
|
||||
0x39, 0xee, 0xb3, 0x7e, 0x59, 0x22, 0x0b, 0xbb, 0x2c, 0x7b, 0xcb, 0xa5,
|
||||
0x07, 0x80, 0xac, 0xe1, 0xcf, 0xf1, 0xe3, 0xfd, 0xbd, 0xab, 0x02, 0xdd,
|
||||
0xa8, 0x68, 0xd7, 0x8e, 0x6e, 0x16, 0x27, 0x3c, 0x67, 0xf4, 0xe8, 0xd1,
|
||||
0x9e, 0x27, 0x8d, 0x19, 0x23, 0x6f, 0x10, 0x55, 0x16, 0x76, 0x59, 0x4e,
|
||||
0xc9, 0x0f, 0x80, 0xac, 0xc6, 0x02, 0xdd, 0xad, 0xa8, 0xa8, 0x10, 0xc3,
|
||||
0x87, 0x0f, 0xf7, 0xac, 0x73, 0xde, 0xbc, 0x79, 0x9e, 0x65, 0xdc, 0x0a,
|
||||
0xcc, 0x9a, 0x35, 0xcb, 0x2d, 0xcb, 0x4a, 0xc7, 0xa2, 0x4f, 0x11, 0xc2,
|
||||
0xb7, 0xb4, 0xa0, 0x0d, 0x6d, 0xcc, 0xb4, 0x7a, 0x7f, 0xa9, 0x6f, 0xfe,
|
||||
0xb0, 0x2f, 0x80, 0x9c, 0xb6, 0xc5, 0x5c, 0x14, 0x39, 0x9d, 0x7f, 0xf2,
|
||||
0xe4, 0xc9, 0x54, 0x59, 0x59, 0x99, 0xeb, 0x85, 0x12, 0xd7, 0xe2, 0xb5,
|
||||
0xb4, 0xb4, 0x38, 0x9d, 0xea, 0x2b, 0xad, 0xa1, 0xa1, 0x21, 0xd5, 0xb7,
|
||||
0x6f, 0x5f, 0xd7, 0xfa, 0x37, 0x6f, 0xde, 0xec, 0xab, 0x1e, 0xbb, 0x50,
|
||||
0x29, 0xfe, 0x82, 0x60, 0xd7, 0xf6, 0x03, 0xc8, 0x7b, 0xc1, 0xbe, 0xb1,
|
||||
0xb9, 0x8d, 0x51, 0x14, 0xd9, 0xe9, 0x1c, 0xf4, 0xd9, 0xbf, 0x7f, 0xbf,
|
||||
0xc0, 0xab, 0x5d, 0x5a, 0xd9, 0x30, 0x63, 0xc6, 0x0c, 0x71, 0xec, 0xd8,
|
||||
0x31, 0x81, 0xb5, 0x78, 0xad, 0xf2, 0xfc, 0x26, 0xf4, 0xe9, 0xd3, 0x47,
|
||||
0x1c, 0x3d, 0x7a, 0x54, 0x8c, 0x1c, 0x39, 0x32, 0xe7, 0x14, 0x04, 0x9d,
|
||||
0xd8, 0xb0, 0x61, 0x83, 0x58, 0xb7, 0x6e, 0x5d, 0x4e, 0x7a, 0x0c, 0x0f,
|
||||
0x9a, 0x39, 0xf5, 0xc6, 0x00, 0xe8, 0x13, 0x43, 0xe3, 0x2d, 0x93, 0x17,
|
||||
0x2f, 0x5e, 0x2c, 0xaa, 0xaa, 0xaa, 0xac, 0x87, 0x31, 0x2e, 0x5e, 0xbc,
|
||||
0x28, 0x7a, 0xf5, 0xea, 0x25, 0x70, 0x0f, 0x2d, 0x66, 0xcf, 0x9e, 0x2d,
|
||||
0x48, 0x54, 0x5b, 0x65, 0xd2, 0xa4, 0x49, 0xe2, 0xd2, 0xa5, 0x4b, 0xe2,
|
||||
0xc8, 0x91, 0x23, 0x82, 0x57, 0xfc, 0x03, 0x07, 0x0e, 0x14, 0xfc, 0x69,
|
||||
0xe0, 0xa8, 0xa0, 0x06, 0x62, 0x7d, 0xf9, 0xaf, 0xc2, 0x11, 0x69, 0xdd,
|
||||
0x8c, 0xdd, 0xfd, 0x39, 0x6d, 0x4b, 0xe9, 0x12, 0x9d, 0xea, 0x89, 0x4b,
|
||||
0x5a, 0x29, 0xfe, 0xca, 0xe4, 0x06, 0x75, 0x5f, 0xe5, 0x4f, 0xc0, 0x13,
|
||||
0xa8, 0x91, 0x64, 0x22, 0xf0, 0x24, 0xd6, 0xd7, 0x00, 0xc9, 0xe4, 0x2c,
|
||||
0x50, 0xaf, 0x9b, 0x19, 0x00, 0x8d, 0x81, 0x56, 0x69, 0x2a, 0x8b, 0x13,
|
||||
0x02, 0x8d, 0x0c, 0x80, 0xdb, 0x71, 0xb2, 0xd8, 0xd8, 0x1a, 0x28, 0x02,
|
||||
0xb7, 0x19, 0x00, 0xdf, 0x04, 0x5a, 0xa5, 0xa9, 0x2c, 0x4e, 0x08, 0x7c,
|
||||
0xc3, 0x00, 0xe0, 0x7b, 0x00, 0x8d, 0x24, 0x13, 0x81, 0x4c, 0x00, 0xf0,
|
||||
0x36, 0xd0, 0x48, 0xb2, 0x10, 0x20, 0xe7, 0xb7, 0xd8, 0x03, 0xb4, 0x40,
|
||||
0xe5, 0x4d, 0x69, 0x25, 0x0b, 0xd4, 0x38, 0x79, 0x4b, 0xce, 0x5b, 0x18,
|
||||
0x00, 0x94, 0x9b, 0xef, 0x36, 0xe6, 0x33, 0x41, 0x08, 0x58, 0x9c, 0x73,
|
||||
0x28, 0x98, 0x72, 0x0a, 0x3a, 0xd7, 0xda, 0x8b, 0xd9, 0xc7, 0x9b, 0x37,
|
||||
0x6f, 0x04, 0x46, 0x02, 0x5d, 0xad, 0xce, 0x7f, 0x18, 0x83, 0xf3, 0xfc,
|
||||
0x85, 0xd6, 0x07, 0xb4, 0x6f, 0xdf, 0x3e, 0x67, 0x08, 0xf9, 0xf5, 0xeb,
|
||||
0xd7, 0xae, 0x75, 0x73, 0xa8, 0x99, 0xe5, 0x63, 0x2a, 0xe4, 0x3c, 0xf3,
|
||||
0x6c, 0xe0, 0xdf, 0x65, 0x39, 0xd1, 0xdc, 0x2c, 0x77, 0xae, 0x69, 0xf9,
|
||||
0xf2, 0xe5, 0x82, 0x2f, 0x77, 0x72, 0xd3, 0xeb, 0xd7, 0xaf, 0xe7, 0xb8,
|
||||
0xb6, 0x75, 0xeb, 0x56, 0xd7, 0xb2, 0xac, 0x63, 0xdf, 0xbe, 0x7d, 0x99,
|
||||
0xf2, 0x3c, 0xd7, 0xad, 0x5e, 0xa6, 0xb3, 0x6d, 0x99, 0x22, 0x19, 0x3b,
|
||||
0x8b, 0x73, 0xfb, 0x27, 0xe0, 0x2c, 0x1c, 0x91, 0xc2, 0xd4, 0xe5, 0xcb,
|
||||
0x97, 0x65, 0x62, 0xa4, 0x75, 0xdd, 0x12, 0xb1, 0x23, 0xd7, 0xe4, 0x3c,
|
||||
0xd3, 0x03, 0xb0, 0x9f, 0xfb, 0x27, 0x13, 0x82, 0x16, 0xcc, 0x99, 0x07,
|
||||
0x5d, 0xa5, 0xd4, 0xfa, 0x0a, 0xfd, 0x9c, 0x48, 0x6d, 0xd8, 0xa1, 0x72,
|
||||
0x89, 0xd8, 0x91, 0x6b, 0xeb, 0xb7, 0xcd, 0xee, 0x01, 0xd8, 0xbc, 0x94,
|
||||
0x9f, 0x01, 0xce, 0xa7, 0xaf, 0x5a, 0xb5, 0xca, 0xd7, 0x02, 0x4e, 0x07,
|
||||
0x0c, 0x42, 0x4f, 0x0a, 0x62, 0x0a, 0xb9, 0xad, 0x46, 0x73, 0xb1, 0x2b,
|
||||
0x31, 0x23, 0x76, 0x92, 0x24, 0xc3, 0xb5, 0x7d, 0x11, 0xc8, 0x76, 0x32,
|
||||
0x89, 0x41, 0x37, 0xba, 0x73, 0xe7, 0x4e, 0xb1, 0x77, 0xef, 0x5e, 0x6b,
|
||||
0x9e, 0x3e, 0x7b, 0x9d, 0xdb, 0xe3, 0xc7, 0x7c, 0x87, 0x71, 0xdb, 0x64,
|
||||
0xcd, 0x9a, 0x35, 0x62, 0xe1, 0xc2, 0x85, 0xae, 0x95, 0x0c, 0x19, 0x32,
|
||||
0x24, 0x27, 0x6f, 0xd1, 0xa2, 0x45, 0x62, 0xd8, 0xb0, 0x61, 0x39, 0x69,
|
||||
0xd9, 0x07, 0xd3, 0xa6, 0x4d, 0xcb, 0x1c, 0xf2, 0xdc, 0x3d, 0x7b, 0xf6,
|
||||
0x64, 0x8e, 0xf3, 0x77, 0x82, 0x78, 0xde, 0x7f, 0xe3, 0xc6, 0x8d, 0x39,
|
||||
0x6f, 0x44, 0xc1, 0x5b, 0xc5, 0x04, 0x57, 0x23, 0x07, 0x81, 0x4d, 0xbe,
|
||||
0xbd, 0x59, 0xc7, 0xae, 0x5c, 0xdf, 0x40, 0x21, 0x69, 0x6b, 0x03, 0xfc,
|
||||
0xd4, 0xcd, 0x39, 0x73, 0x5d, 0xc5, 0x69, 0x3d, 0x80, 0x1f, 0x4c, 0x02,
|
||||
0x2e, 0x43, 0x8e, 0x33, 0x92, 0xfd, 0x13, 0xc0, 0xc4, 0x3f, 0x66, 0x72,
|
||||
0xcc, 0x8e, 0xae, 0x08, 0xe4, 0x70, 0x9c, 0x1f, 0x00, 0x7f, 0x82, 0xd7,
|
||||
0xc5, 0x2f, 0xa2, 0xd7, 0x15, 0x2a, 0xfd, 0xfc, 0x22, 0xb7, 0xe4, 0x38,
|
||||
0x23, 0xf9, 0x01, 0x50, 0x87, 0x9c, 0xe3, 0x99, 0xdc, 0x08, 0x76, 0x0a,
|
||||
0x0d, 0xbc, 0x44, 0x60, 0x4e, 0xa0, 0x4d, 0x2a, 0xf0, 0x44, 0x31, 0xb9,
|
||||
0x25, 0xc7, 0x19, 0xc9, 0x0f, 0x00, 0x66, 0xe4, 0x74, 0x11, 0x99, 0x92,
|
||||
0x21, 0xed, 0xe0, 0x0f, 0x1c, 0x43, 0x6a, 0x29, 0xfc, 0x66, 0x1e, 0x3d,
|
||||
0x7a, 0x14, 0x7e, 0xa3, 0xb9, 0x2d, 0xb6, 0xe2, 0xd6, 0x29, 0x00, 0x0e,
|
||||
0xe2, 0x1c, 0xfe, 0xb9, 0x40, 0x24, 0x22, 0x71, 0xf0, 0x23, 0x12, 0x7f,
|
||||
0xb2, 0x1b, 0x8d, 0xd8, 0x37, 0x72, 0x4a, 0x6e, 0x73, 0xc4, 0x29, 0x00,
|
||||
0x9e, 0xa3, 0xc4, 0xef, 0x72, 0x4a, 0x85, 0x78, 0x70, 0xe0, 0xc0, 0x81,
|
||||
0x10, 0x5b, 0x0b, 0xb7, 0xa9, 0x88, 0x7d, 0x23, 0xa7, 0xe4, 0xd6, 0x97,
|
||||
0xf4, 0x42, 0xa9, 0x26, 0x68, 0x24, 0xb7, 0x84, 0x18, 0x00, 0xd1, 0xee,
|
||||
0x4e, 0x90, 0x3e, 0x45, 0x85, 0x67, 0x9a, 0x4b, 0x72, 0x5a, 0x94, 0xfc,
|
||||
0x1a, 0xa5, 0x23, 0x31, 0x1a, 0x03, 0x35, 0x29, 0x3e, 0x96, 0xa5, 0x8b,
|
||||
0xd0, 0x17, 0xfa, 0x14, 0x15, 0x9e, 0x68, 0x97, 0x5c, 0x16, 0x2d, 0xfc,
|
||||
0x8f, 0x19, 0x4e, 0x1a, 0x44, 0x62, 0xf8, 0x94, 0x29, 0x53, 0x8a, 0x7e,
|
||||
0x05, 0x8b, 0x8a, 0x01, 0xc3, 0xff, 0x0b, 0xa0, 0x2f, 0x51, 0xe1, 0x98,
|
||||
0xe6, 0x90, 0x5c, 0x96, 0x24, 0x5b, 0x71, 0x56, 0x64, 0xc6, 0x63, 0x28,
|
||||
0x36, 0x85, 0x21, 0xe4, 0x56, 0xaf, 0x96, 0x51, 0x91, 0xe8, 0x7c, 0x9b,
|
||||
0xb0, 0xe6, 0xc0, 0xb2, 0x9d, 0x3e, 0x44, 0x89, 0x21, 0xda, 0x26, 0x87,
|
||||
0xae, 0x52, 0xe6, 0x9a, 0xf3, 0x2e, 0x83, 0xff, 0xcc, 0xc4, 0x45, 0xa3,
|
||||
0xad, 0x9f, 0xbe, 0xf4, 0x38, 0x31, 0xc8, 0x6c, 0xbc, 0x48, 0x49, 0x2c,
|
||||
0x58, 0xb0, 0x40, 0xe0, 0x25, 0x4a, 0x25, 0xff, 0x59, 0x54, 0x90, 0xf6,
|
||||
0x14, 0xaa, 0x8b, 0x7f, 0x1e, 0x85, 0xf7, 0x12, 0x89, 0xc3, 0x87, 0x0f,
|
||||
0x0b, 0x99, 0x2f, 0x8f, 0x28, 0x64, 0x43, 0x56, 0x9e, 0xe7, 0x9f, 0x47,
|
||||
0x67, 0x95, 0x75, 0xdd, 0xfd, 0x0c, 0x39, 0x51, 0x47, 0xb1, 0x69, 0xbf,
|
||||
0x34, 0x0e, 0x96, 0xbb, 0xb2, 0x5a, 0x64, 0x46, 0xad, 0x09, 0x82, 0xd8,
|
||||
0x7d, 0x09, 0xc8, 0x99, 0xa7, 0x78, 0xfd, 0x04, 0xd8, 0x15, 0x7c, 0x80,
|
||||
0x9d, 0x2b, 0xd0, 0x4e, 0x76, 0x82, 0xd9, 0x2a, 0x8d, 0xc0, 0x4b, 0x58,
|
||||
0xc7, 0xe7, 0xd7, 0x3d, 0x1f, 0xfa, 0xf1, 0xbb, 0xa2, 0xb1, 0x01, 0x95,
|
||||
0xbd, 0x07, 0x9d, 0x01, 0x35, 0xa2, 0x3e, 0x02, 0xbc, 0xf0, 0xfb, 0x6b,
|
||||
0xd0, 0x66, 0x32, 0x00, 0xa4, 0xbe, 0x4b, 0x00, 0xf5, 0x9b, 0xdf, 0xfa,
|
||||
0xb6, 0x63, 0x40, 0x8e, 0xc8, 0x95, 0x14, 0xe1, 0x6b, 0xb3, 0x9e, 0x40,
|
||||
0x0d, 0x51, 0x6a, 0x62, 0x40, 0x6e, 0xc8, 0x91, 0x54, 0xf9, 0x14, 0xb5,
|
||||
0x9b, 0x00, 0x50, 0x13, 0x03, 0x72, 0x13, 0x8a, 0xfc, 0x1e, 0xad, 0x98,
|
||||
0x20, 0x50, 0x0b, 0x03, 0x72, 0x12, 0x9a, 0xf0, 0x6e, 0x80, 0xef, 0x51,
|
||||
0x35, 0x41, 0xa0, 0x06, 0x06, 0xe4, 0x22, 0xf4, 0x3b, 0x34, 0xfe, 0xe5,
|
||||
0xc8, 0x5d, 0x13, 0x04, 0x91, 0x7f, 0x09, 0xc8, 0x01, 0xb9, 0x88, 0x44,
|
||||
0x78, 0xc1, 0x51, 0x0f, 0x35, 0x3d, 0x41, 0x34, 0x18, 0x10, 0x7b, 0xe9,
|
||||
0x17, 0x7d, 0x5e, 0x91, 0x35, 0x09, 0x05, 0xcc, 0x9d, 0x41, 0xf8, 0x01,
|
||||
0x40, 0xcc, 0x89, 0xbd, 0x12, 0x52, 0x0d, 0x2b, 0xbe, 0x87, 0x9a, 0x9e,
|
||||
0x20, 0x1c, 0x0c, 0x88, 0x35, 0x31, 0x57, 0x4a, 0x3e, 0x81, 0x35, 0x26,
|
||||
0x08, 0xe4, 0x07, 0x00, 0x31, 0x26, 0xd6, 0x4a, 0x4a, 0x35, 0xac, 0x8a,
|
||||
0x6c, 0x29, 0x19, 0xda, 0xd6, 0xbd, 0x07, 0x22, 0xb6, 0xc4, 0x58, 0x69,
|
||||
0xe1, 0x8b, 0xfc, 0xef, 0x41, 0x75, 0x27, 0x23, 0x6c, 0xff, 0x88, 0xa9,
|
||||
0xbf, 0x3f, 0x49, 0x50, 0x20, 0x3c, 0xde, 0x87, 0x0d, 0x91, 0x3f, 0x67,
|
||||
0xa8, 0x51, 0x10, 0x12, 0x4b, 0x62, 0x1a, 0x2b, 0xe1, 0x6a, 0xa2, 0x5a,
|
||||
0x68, 0xd8, 0xdf, 0x14, 0xdd, 0xda, 0x23, 0x86, 0xc4, 0x32, 0x96, 0xc2,
|
||||
0xe9, 0xe6, 0x2f, 0xa0, 0x6f, 0xa0, 0xba, 0x11, 0x23, 0xdb, 0x1f, 0x62,
|
||||
0x46, 0xec, 0xfc, 0x4e, 0xd9, 0xa3, 0xa8, 0xba, 0x52, 0x0d, 0xd3, 0xf8,
|
||||
0x4c, 0x9a, 0x6c, 0xd0, 0x74, 0xa9, 0x9f, 0x58, 0x11, 0x33, 0xad, 0xa4,
|
||||
0x2f, 0xbc, 0xf9, 0x0a, 0xaa, 0x0b, 0x49, 0xb2, 0xfc, 0x20, 0x46, 0xc4,
|
||||
0x4a, 0x4b, 0x29, 0x83, 0x57, 0xab, 0xa0, 0xdf, 0x41, 0x65, 0x01, 0x18,
|
||||
0xd7, 0x7a, 0x89, 0x09, 0xb1, 0x21, 0x46, 0xda, 0x0b, 0x23, 0xfc, 0x0f,
|
||||
0xd0, 0xb7, 0xd0, 0xb8, 0x12, 0x16, 0x94, 0xdd, 0xc4, 0x80, 0x58, 0x68,
|
||||
0xfb, 0xad, 0x87, 0x6f, 0xae, 0x52, 0x89, 0x9c, 0x24, 0x4f, 0x2b, 0xd3,
|
||||
0x77, 0x62, 0x90, 0x68, 0xe1, 0x13, 0xca, 0x9f, 0x43, 0xbf, 0x85, 0x06,
|
||||
0xf5, 0xad, 0x52, 0xbd, 0x1e, 0xfa, 0x4a, 0x9f, 0xe9, 0xbb, 0x91, 0x34,
|
||||
0x02, 0xbc, 0xdd, 0xf9, 0x29, 0x94, 0xcb, 0xcf, 0x55, 0x27, 0xb0, 0x54,
|
||||
0xfb, 0xe8, 0x1b, 0x7d, 0xd4, 0xe2, 0xd6, 0x0e, 0x7e, 0x48, 0x93, 0x8f,
|
||||
0x51, 0xf3, 0x69, 0x68, 0xa9, 0x40, 0xab, 0x76, 0x1e, 0x7d, 0xa1, 0x4f,
|
||||
0x46, 0x8a, 0x44, 0xe0, 0x23, 0x94, 0xff, 0x2d, 0x94, 0xff, 0xe2, 0xac,
|
||||
0x1a, 0xa9, 0x5e, 0xf6, 0xd0, 0x66, 0xda, 0x4e, 0x1f, 0x8c, 0xb4, 0x11,
|
||||
0x01, 0x76, 0x99, 0x35, 0xd0, 0xdd, 0xd0, 0xa7, 0x50, 0x2f, 0xf0, 0xa3,
|
||||
0xca, 0xa7, 0x6d, 0xb4, 0x91, 0xb6, 0xc6, 0xa2, 0x9b, 0x8f, 0xe3, 0x3d,
|
||||
0x67, 0x37, 0x80, 0x3b, 0x0b, 0x3a, 0x33, 0xad, 0x1f, 0x62, 0x1b, 0xd5,
|
||||
0xc5, 0x14, 0x6f, 0xe1, 0xae, 0x42, 0x6b, 0xd3, 0x7a, 0x12, 0x5b, 0xfe,
|
||||
0x01, 0x47, 0x6c, 0x24, 0x8e, 0x01, 0x90, 0x0f, 0x2e, 0x27, 0x4a, 0xaa,
|
||||
0xa0, 0x0c, 0x08, 0xde, 0x52, 0x55, 0x40, 0x65, 0x4d, 0x9e, 0xf0, 0x2f,
|
||||
0xf6, 0xf8, 0xb8, 0x3c, 0xdf, 0xb4, 0x4d, 0xd2, 0xbf, 0x86, 0x32, 0x2d,
|
||||
0xb6, 0xa2, 0x43, 0x00, 0x38, 0x81, 0xcf, 0x41, 0x15, 0x06, 0x02, 0x17,
|
||||
0x4c, 0x72, 0x3b, 0x02, 0xca, 0x77, 0xe4, 0x74, 0x87, 0x96, 0xa7, 0xd5,
|
||||
0xde, 0xc7, 0xa1, 0xf5, 0x26, 0x14, 0x76, 0xdf, 0x7c, 0x23, 0x0a, 0x95,
|
||||
0xfb, 0x5c, 0x7c, 0x71, 0x07, 0x4a, 0xc2, 0x6f, 0xa5, 0xb7, 0x7c, 0x46,
|
||||
0x52, 0x2b, 0xf9, 0x3f, 0x92, 0xc9, 0x00, 0xb6, 0x61, 0xee, 0xab, 0xc9,
|
||||
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
|
||||
}
|
||||
12
app/users/constants.go
Обычный файл
12
app/users/constants.go
Обычный файл
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
const (
|
||||
TokenTypePasswordRecovery = "password_recovery"
|
||||
TokenTypeVerifyEmail = "verify_email"
|
||||
TokenTypeTeamInvitation = "team_invitation"
|
||||
TokenTypeGuestInvitation = "guest_invitation"
|
||||
InvitationExpiryTime = 1000 * 60 * 60 * 48 // 48 hours
|
||||
)
|
||||
43
app/users/errors.go
Обычный файл
43
app/users/errors.go
Обычный файл
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
AcceptedDomainError = errors.New("the email provided does not belong to an accepted domain")
|
||||
VerifyUserError = errors.New("could not update verify email field")
|
||||
UserCountError = errors.New("could not get the total number of the users.")
|
||||
UserCreationDisabledError = errors.New("user creation is not allowed")
|
||||
UserStoreIsEmptyError = errors.New("could not check if the user store is empty")
|
||||
|
||||
GetTokenError = errors.New("could not get token")
|
||||
GetSessionError = errors.New("could not get session")
|
||||
DeleteTokenError = errors.New("could not delete token")
|
||||
DeleteSessionError = errors.New("could not delete session")
|
||||
DeleteAllAccessDataError = errors.New("could not delete all access data")
|
||||
|
||||
DefaultFontError = errors.New("could not get default font")
|
||||
UserInitialsError = errors.New("could not get user initials")
|
||||
ImageEncodingError = errors.New("could not encode image")
|
||||
)
|
||||
|
||||
// ErrInvalidPassword indicates an error against the password settings
|
||||
type ErrInvalidPassword struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func NewErrInvalidPassword(id string) *ErrInvalidPassword {
|
||||
return &ErrInvalidPassword{
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ErrInvalidPassword) Error() string {
|
||||
return "invalid password"
|
||||
}
|
||||
|
||||
func (e *ErrInvalidPassword) Id() string {
|
||||
return e.id
|
||||
}
|
||||
175
app/users/helper_test.go
Обычный файл
175
app/users/helper_test.go
Обычный файл
@@ -0,0 +1,175 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/services/cache"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
)
|
||||
|
||||
var initBasicOnce sync.Once
|
||||
|
||||
type TestHelper struct {
|
||||
service *UserService
|
||||
configStore *config.Store
|
||||
dbStore store.Store
|
||||
workspace string
|
||||
|
||||
Context *request.Context
|
||||
BasicUser *model.User
|
||||
BasicUser2 *model.User
|
||||
|
||||
SystemAdminUser *model.User
|
||||
LogBuffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func Setup(tb testing.TB) *TestHelper {
|
||||
if testing.Short() {
|
||||
tb.SkipNow()
|
||||
}
|
||||
dbStore := mainHelper.GetStore()
|
||||
dbStore.DropAllTables()
|
||||
dbStore.MarkSystemRanUnitTests()
|
||||
mainHelper.PreloadMigrations()
|
||||
|
||||
return setupTestHelper(dbStore, false, tb)
|
||||
}
|
||||
|
||||
func setupTestHelper(s store.Store, includeCacheLayer bool, tb testing.TB) *TestHelper {
|
||||
tempWorkspace, err := ioutil.TempDir("", "userservicetest")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configStore := config.NewTestMemoryStore()
|
||||
|
||||
config := configStore.Get()
|
||||
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
||||
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
||||
*config.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||
*config.LogSettings.EnableSentry = false // disable error reporting during tests
|
||||
*config.AnnouncementSettings.AdminNoticesEnabled = false
|
||||
*config.AnnouncementSettings.UserNoticesEnabled = false
|
||||
*config.TeamSettings.MaxUsersPerTeam = 50
|
||||
*config.RateLimitSettings.Enable = false
|
||||
*config.TeamSettings.EnableOpenServer = true
|
||||
// Disable strict password requirements for test
|
||||
*config.PasswordSettings.MinimumLength = 5
|
||||
*config.PasswordSettings.Lowercase = false
|
||||
*config.PasswordSettings.Uppercase = false
|
||||
*config.PasswordSettings.Symbol = false
|
||||
*config.PasswordSettings.Number = false
|
||||
configStore.Set(config)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
provider := cache.NewProvider()
|
||||
cache, err := provider.NewCache(&cache.CacheOptions{
|
||||
Size: model.SessionCacheSize,
|
||||
Striped: true,
|
||||
StripedBuckets: maxInt(runtime.NumCPU()-1, 1),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &TestHelper{
|
||||
service: &UserService{
|
||||
store: s.User(),
|
||||
sessionStore: s.Session(),
|
||||
oAuthStore: s.OAuth(),
|
||||
sessionCache: cache,
|
||||
config: configStore.Get,
|
||||
sessionPool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &model.Session{}
|
||||
},
|
||||
},
|
||||
},
|
||||
Context: &request.Context{},
|
||||
configStore: configStore,
|
||||
dbStore: s,
|
||||
LogBuffer: buffer,
|
||||
workspace: tempWorkspace,
|
||||
}
|
||||
}
|
||||
|
||||
func (th *TestHelper) InitBasic() *TestHelper {
|
||||
// create users once and cache them because password hashing is slow
|
||||
initBasicOnce.Do(func() {
|
||||
th.SystemAdminUser = th.CreateUser()
|
||||
th.SystemAdminUser, _ = th.service.GetUser(th.SystemAdminUser.Id)
|
||||
|
||||
th.BasicUser = th.CreateUser()
|
||||
th.BasicUser, _ = th.service.GetUser(th.BasicUser.Id)
|
||||
|
||||
th.BasicUser2 = th.CreateUser()
|
||||
th.BasicUser2, _ = th.service.GetUser(th.BasicUser2.Id)
|
||||
})
|
||||
|
||||
return th
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateUser() *model.User {
|
||||
return th.CreateUserOrGuest(false)
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateGuest() *model.User {
|
||||
return th.CreateUserOrGuest(true)
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateUserOrGuest(guest bool) *model.User {
|
||||
id := model.NewId()
|
||||
|
||||
user := &model.User{
|
||||
Email: "success+" + id + "@simulator.amazonses.com",
|
||||
Username: "un_" + id,
|
||||
Nickname: "nn_" + id,
|
||||
Password: "Password1",
|
||||
EmailVerified: true,
|
||||
}
|
||||
|
||||
var err error
|
||||
if guest {
|
||||
if user, err = th.service.CreateUser(user, UserCreateOptions{Guest: true}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
if user, err = th.service.CreateUser(user, UserCreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func (th *TestHelper) TearDown() {
|
||||
th.configStore.Close()
|
||||
|
||||
th.dbStore.Close()
|
||||
|
||||
if th.workspace != "" {
|
||||
os.RemoveAll(th.workspace)
|
||||
}
|
||||
}
|
||||
|
||||
func (th *TestHelper) UpdateConfig(f func(*model.Config)) {
|
||||
if th.configStore.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
old := th.configStore.Get()
|
||||
updated := old.Clone()
|
||||
f(updated)
|
||||
if _, _, err := th.configStore.Set(updated); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
32
app/users/main_test.go
Обычный файл
32
app/users/main_test.go
Обычный файл
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
var mainHelper *testlib.MainHelper
|
||||
var replicaFlag bool
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if f := flag.Lookup("mysql-replica"); f == nil {
|
||||
flag.BoolVar(&replicaFlag, "mysql-replica", false, "")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
var options = testlib.HelperOptions{
|
||||
EnableStore: true,
|
||||
EnableResources: true,
|
||||
WithReadReplica: replicaFlag,
|
||||
}
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
|
||||
mainHelper.Main(m)
|
||||
}
|
||||
96
app/users/password.go
Обычный файл
96
app/users/password.go
Обычный файл
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func CheckUserPassword(user *model.User, password string) error {
|
||||
if err := ComparePassword(user.Password, password); err != nil {
|
||||
return NewErrInvalidPassword("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HashPassword generates a hash using the bcrypt.GenerateFromPassword
|
||||
func HashPassword(password string) string {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return string(hash)
|
||||
}
|
||||
|
||||
func ComparePassword(hash string, password string) error {
|
||||
if password == "" || hash == "" {
|
||||
return errors.New("empty password or hash")
|
||||
}
|
||||
|
||||
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
}
|
||||
|
||||
func (us *UserService) isPasswordValid(password string) error {
|
||||
|
||||
if *us.config().ServiceSettings.EnableDeveloper {
|
||||
return nil
|
||||
}
|
||||
|
||||
return IsPasswordValidWithSettings(password, &us.config().PasswordSettings)
|
||||
}
|
||||
|
||||
// IsPasswordValidWithSettings is a utility functions that checks if the given password
|
||||
// comforms to the password settings. It returns the error id as error value.
|
||||
func IsPasswordValidWithSettings(password string, settings *model.PasswordSettings) error {
|
||||
id := "model.user.is_valid.pwd"
|
||||
isError := false
|
||||
|
||||
if len(password) < *settings.MinimumLength || len(password) > model.PasswordMaximumLength {
|
||||
isError = true
|
||||
}
|
||||
|
||||
if *settings.Lowercase {
|
||||
if !strings.ContainsAny(password, model.LowercaseLetters) {
|
||||
isError = true
|
||||
}
|
||||
|
||||
id = id + "_lowercase"
|
||||
}
|
||||
|
||||
if *settings.Uppercase {
|
||||
if !strings.ContainsAny(password, model.UppercaseLetters) {
|
||||
isError = true
|
||||
}
|
||||
|
||||
id = id + "_uppercase"
|
||||
}
|
||||
|
||||
if *settings.Number {
|
||||
if !strings.ContainsAny(password, model.NUMBERS) {
|
||||
isError = true
|
||||
}
|
||||
|
||||
id = id + "_number"
|
||||
}
|
||||
|
||||
if *settings.Symbol {
|
||||
if !strings.ContainsAny(password, model.SYMBOLS) {
|
||||
isError = true
|
||||
}
|
||||
|
||||
id = id + "_symbol"
|
||||
}
|
||||
|
||||
if isError {
|
||||
return NewErrInvalidPassword(id + ".app_error")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
140
app/users/password_test.go
Обычный файл
140
app/users/password_test.go
Обычный файл
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func TestComparePassword(t *testing.T) {
|
||||
hash := HashPassword("Test")
|
||||
|
||||
assert.NoError(t, ComparePassword(hash, "Test"), "Passwords don't match")
|
||||
assert.Error(t, ComparePassword(hash, "Test2"), "Passwords should not have matched")
|
||||
}
|
||||
|
||||
func TestIsPasswordValidWithSettings(t *testing.T) {
|
||||
for name, tc := range map[string]struct {
|
||||
Password string
|
||||
Settings *model.PasswordSettings
|
||||
ExpectedError string
|
||||
}{
|
||||
"Short": {
|
||||
Password: strings.Repeat("x", 3),
|
||||
Settings: &model.PasswordSettings{
|
||||
MinimumLength: model.NewInt(3),
|
||||
Lowercase: model.NewBool(false),
|
||||
Uppercase: model.NewBool(false),
|
||||
Number: model.NewBool(false),
|
||||
Symbol: model.NewBool(false),
|
||||
},
|
||||
},
|
||||
"Long": {
|
||||
Password: strings.Repeat("x", model.PasswordMaximumLength),
|
||||
Settings: &model.PasswordSettings{
|
||||
Lowercase: model.NewBool(false),
|
||||
Uppercase: model.NewBool(false),
|
||||
Number: model.NewBool(false),
|
||||
Symbol: model.NewBool(false),
|
||||
},
|
||||
},
|
||||
"TooShort": {
|
||||
Password: strings.Repeat("x", 2),
|
||||
Settings: &model.PasswordSettings{
|
||||
MinimumLength: model.NewInt(3),
|
||||
Lowercase: model.NewBool(false),
|
||||
Uppercase: model.NewBool(false),
|
||||
Number: model.NewBool(false),
|
||||
Symbol: model.NewBool(false),
|
||||
},
|
||||
ExpectedError: "model.user.is_valid.pwd.app_error",
|
||||
},
|
||||
"TooLong": {
|
||||
Password: strings.Repeat("x", model.PasswordMaximumLength+1),
|
||||
Settings: &model.PasswordSettings{
|
||||
Lowercase: model.NewBool(false),
|
||||
Uppercase: model.NewBool(false),
|
||||
Number: model.NewBool(false),
|
||||
Symbol: model.NewBool(false),
|
||||
},
|
||||
ExpectedError: "model.user.is_valid.pwd.app_error",
|
||||
},
|
||||
"MissingLower": {
|
||||
Password: "AAAAAAAAAAASD123!@#",
|
||||
Settings: &model.PasswordSettings{
|
||||
Lowercase: model.NewBool(true),
|
||||
Uppercase: model.NewBool(false),
|
||||
Number: model.NewBool(false),
|
||||
Symbol: model.NewBool(false),
|
||||
},
|
||||
ExpectedError: "model.user.is_valid.pwd_lowercase.app_error",
|
||||
},
|
||||
"MissingUpper": {
|
||||
Password: "aaaaaaaaaaaaasd123!@#",
|
||||
Settings: &model.PasswordSettings{
|
||||
Uppercase: model.NewBool(true),
|
||||
Lowercase: model.NewBool(false),
|
||||
Number: model.NewBool(false),
|
||||
Symbol: model.NewBool(false),
|
||||
},
|
||||
ExpectedError: "model.user.is_valid.pwd_uppercase.app_error",
|
||||
},
|
||||
"MissingNumber": {
|
||||
Password: "asasdasdsadASD!@#",
|
||||
Settings: &model.PasswordSettings{
|
||||
Number: model.NewBool(true),
|
||||
Lowercase: model.NewBool(false),
|
||||
Uppercase: model.NewBool(false),
|
||||
Symbol: model.NewBool(false),
|
||||
},
|
||||
ExpectedError: "model.user.is_valid.pwd_number.app_error",
|
||||
},
|
||||
"MissingSymbol": {
|
||||
Password: "asdasdasdasdasdASD123",
|
||||
Settings: &model.PasswordSettings{
|
||||
Symbol: model.NewBool(true),
|
||||
Lowercase: model.NewBool(false),
|
||||
Uppercase: model.NewBool(false),
|
||||
Number: model.NewBool(false),
|
||||
},
|
||||
ExpectedError: "model.user.is_valid.pwd_symbol.app_error",
|
||||
},
|
||||
"MissingMultiple": {
|
||||
Password: "asdasdasdasdasdasd",
|
||||
Settings: &model.PasswordSettings{
|
||||
Lowercase: model.NewBool(true),
|
||||
Uppercase: model.NewBool(true),
|
||||
Number: model.NewBool(true),
|
||||
Symbol: model.NewBool(true),
|
||||
},
|
||||
ExpectedError: "model.user.is_valid.pwd_lowercase_uppercase_number_symbol.app_error",
|
||||
},
|
||||
"Everything": {
|
||||
Password: "asdASD!@#123",
|
||||
Settings: &model.PasswordSettings{
|
||||
Lowercase: model.NewBool(true),
|
||||
Uppercase: model.NewBool(true),
|
||||
Number: model.NewBool(true),
|
||||
Symbol: model.NewBool(true),
|
||||
},
|
||||
},
|
||||
} {
|
||||
tc.Settings.SetDefaults()
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if err := IsPasswordValidWithSettings(tc.Password, tc.Settings); tc.ExpectedError == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
invErr, ok := err.(*ErrInvalidPassword)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, tc.ExpectedError, invErr.Id())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
182
app/users/profile_picture.go
Обычный файл
182
app/users/profile_picture.go
Обычный файл
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"hash/fnv"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/freetype"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/filestore"
|
||||
"github.com/mattermost/mattermost-server/v6/utils/fileutils"
|
||||
)
|
||||
|
||||
const (
|
||||
imageProfilePixelDimension = 128
|
||||
)
|
||||
|
||||
func (us *UserService) GetProfileImage(user *model.User) ([]byte, bool, error) {
|
||||
if *us.config().FileSettings.DriverName == "" {
|
||||
img, err := us.GetDefaultProfileImage(user)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return img, false, nil
|
||||
}
|
||||
|
||||
path := path.Join("users", user.Id, "profile.png")
|
||||
data, err := us.ReadFile(path)
|
||||
if err != nil {
|
||||
img, appErr := us.GetDefaultProfileImage(user)
|
||||
if appErr != nil {
|
||||
return nil, false, appErr
|
||||
}
|
||||
|
||||
if user.LastPictureUpdate == 0 {
|
||||
if _, err := us.writeFile(bytes.NewReader(img), path); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
return img, true, nil
|
||||
}
|
||||
|
||||
return data, false, nil
|
||||
}
|
||||
|
||||
func (us *UserService) FileBackend() (filestore.FileBackend, error) {
|
||||
license := us.license()
|
||||
backend, err := filestore.NewFileBackend(us.config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (us *UserService) ReadFile(path string) ([]byte, error) {
|
||||
backend, err := us.FileBackend()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, nErr := backend.ReadFile(path)
|
||||
if nErr != nil {
|
||||
return nil, nErr
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (us *UserService) writeFile(fr io.Reader, path string) (int64, error) {
|
||||
backend, err := us.FileBackend()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
result, nErr := backend.WriteFile(fr, path)
|
||||
if nErr != nil {
|
||||
return result, nErr
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (us *UserService) GetDefaultProfileImage(user *model.User) ([]byte, error) {
|
||||
if user.IsBot {
|
||||
return botDefaultImage, nil
|
||||
}
|
||||
|
||||
return createProfileImage(user.Username, user.Id, *us.config().FileSettings.InitialFont)
|
||||
}
|
||||
|
||||
func createProfileImage(username string, userID string, initialFont string) ([]byte, error) {
|
||||
colors := []color.NRGBA{
|
||||
{197, 8, 126, 255},
|
||||
{227, 207, 18, 255},
|
||||
{28, 181, 105, 255},
|
||||
{35, 188, 224, 255},
|
||||
{116, 49, 196, 255},
|
||||
{197, 8, 126, 255},
|
||||
{197, 19, 19, 255},
|
||||
{250, 134, 6, 255},
|
||||
{227, 207, 18, 255},
|
||||
{123, 201, 71, 255},
|
||||
{28, 181, 105, 255},
|
||||
{35, 188, 224, 255},
|
||||
{116, 49, 196, 255},
|
||||
{197, 8, 126, 255},
|
||||
{197, 19, 19, 255},
|
||||
{250, 134, 6, 255},
|
||||
{227, 207, 18, 255},
|
||||
{123, 201, 71, 255},
|
||||
{28, 181, 105, 255},
|
||||
{35, 188, 224, 255},
|
||||
{116, 49, 196, 255},
|
||||
{197, 8, 126, 255},
|
||||
{197, 19, 19, 255},
|
||||
{250, 134, 6, 255},
|
||||
{227, 207, 18, 255},
|
||||
{123, 201, 71, 255},
|
||||
}
|
||||
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(userID))
|
||||
seed := h.Sum32()
|
||||
|
||||
initial := string(strings.ToUpper(username)[0])
|
||||
|
||||
font, err := getFont(initialFont)
|
||||
if err != nil {
|
||||
return nil, DefaultFontError
|
||||
}
|
||||
|
||||
color := colors[int64(seed)%int64(len(colors))]
|
||||
dstImg := image.NewRGBA(image.Rect(0, 0, imageProfilePixelDimension, imageProfilePixelDimension))
|
||||
srcImg := image.White
|
||||
draw.Draw(dstImg, dstImg.Bounds(), &image.Uniform{color}, image.Point{}, draw.Src)
|
||||
size := float64(imageProfilePixelDimension / 2)
|
||||
|
||||
c := freetype.NewContext()
|
||||
c.SetFont(font)
|
||||
c.SetFontSize(size)
|
||||
c.SetClip(dstImg.Bounds())
|
||||
c.SetDst(dstImg)
|
||||
c.SetSrc(srcImg)
|
||||
|
||||
pt := freetype.Pt(imageProfilePixelDimension/5, imageProfilePixelDimension*2/3)
|
||||
_, err = c.DrawString(initial, pt)
|
||||
if err != nil {
|
||||
return nil, UserInitialsError
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if imgErr := png.Encode(buf, dstImg); imgErr != nil {
|
||||
return nil, ImageEncodingError
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func getFont(initialFont string) (*truetype.Font, error) {
|
||||
// Some people have the old default font still set, so just treat that as if they're using the new default
|
||||
if initialFont == "luximbi.ttf" {
|
||||
initialFont = "nunito-bold.ttf"
|
||||
}
|
||||
|
||||
fontDir, _ := fileutils.FindDir("fonts")
|
||||
fontBytes, err := ioutil.ReadFile(filepath.Join(fontDir, initialFont))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return freetype.ParseFont(fontBytes)
|
||||
}
|
||||
26
app/users/profile_picture_test.go
Обычный файл
26
app/users/profile_picture_test.go
Обычный файл
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateProfileImage(t *testing.T) {
|
||||
b, err := createProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba", "nunito-bold.ttf")
|
||||
require.NoError(t, err)
|
||||
|
||||
rdr := bytes.NewReader(b)
|
||||
img, _, err2 := image.Decode(rdr)
|
||||
require.NoError(t, err2)
|
||||
|
||||
colorful := color.RGBA{116, 49, 196, 255}
|
||||
|
||||
require.Equal(t, colorful, img.At(1, 1), "Failed to create correct color")
|
||||
}
|
||||
85
app/users/service.go
Обычный файл
85
app/users/service.go
Обычный файл
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/services/cache"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
store store.UserStore
|
||||
sessionStore store.SessionStore
|
||||
oAuthStore store.OAuthStore
|
||||
sessionCache cache.Cache
|
||||
sessionPool sync.Pool
|
||||
metrics einterfaces.MetricsInterface
|
||||
cluster einterfaces.ClusterInterface
|
||||
config func() *model.Config
|
||||
license func() *model.License
|
||||
}
|
||||
|
||||
// ServiceConfig is used to initialize the UserService.
|
||||
type ServiceConfig struct {
|
||||
// Mandatory fields
|
||||
UserStore store.UserStore
|
||||
SessionStore store.SessionStore
|
||||
OAuthStore store.OAuthStore
|
||||
ConfigFn func() *model.Config
|
||||
LicenseFn func() *model.License
|
||||
// Optional fields
|
||||
Metrics einterfaces.MetricsInterface
|
||||
Cluster einterfaces.ClusterInterface
|
||||
}
|
||||
|
||||
func New(c ServiceConfig) (*UserService, error) {
|
||||
if err := c.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cacheProvider := cache.NewProvider()
|
||||
if err := cacheProvider.Connect(); err != nil {
|
||||
return nil, fmt.Errorf("could not connect to cache provider: %w", err)
|
||||
}
|
||||
|
||||
sessionCache, err := cacheProvider.NewCache(&cache.CacheOptions{
|
||||
Size: model.SessionCacheSize,
|
||||
Striped: true,
|
||||
StripedBuckets: maxInt(runtime.NumCPU()-1, 1),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create session cache: %w", err)
|
||||
}
|
||||
|
||||
return &UserService{
|
||||
store: c.UserStore,
|
||||
sessionStore: c.SessionStore,
|
||||
oAuthStore: c.OAuthStore,
|
||||
config: c.ConfigFn,
|
||||
license: c.LicenseFn,
|
||||
metrics: c.Metrics,
|
||||
cluster: c.Cluster,
|
||||
sessionCache: sessionCache,
|
||||
sessionPool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &model.Session{}
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ServiceConfig) validate() error {
|
||||
if c.ConfigFn == nil || c.UserStore == nil || c.SessionStore == nil || c.OAuthStore == nil || c.LicenseFn == nil {
|
||||
return errors.New("required parameters are not provided")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
35
app/users/service_test.go
Обычный файл
35
app/users/service_test.go
Обычный файл
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
_, err := New(ServiceConfig{})
|
||||
require.Error(t, err)
|
||||
|
||||
dbStore := mainHelper.GetStore()
|
||||
|
||||
cfn := func() *model.Config {
|
||||
return &model.Config{}
|
||||
}
|
||||
|
||||
lfn := func() *model.License {
|
||||
return model.NewTestLicense()
|
||||
}
|
||||
|
||||
_, err = New(ServiceConfig{
|
||||
UserStore: dbStore.User(),
|
||||
SessionStore: dbStore.Session(),
|
||||
OAuthStore: dbStore.OAuth(),
|
||||
ConfigFn: cfn,
|
||||
LicenseFn: lfn,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
249
app/users/session.go
Обычный файл
249
app/users/session.go
Обычный файл
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/store/sqlstore"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (us *UserService) ReturnSessionToPool(session *model.Session) {
|
||||
if session != nil {
|
||||
session.Id = ""
|
||||
us.sessionPool.Put(session)
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UserService) CreateSession(session *model.Session) (*model.Session, error) {
|
||||
session.Token = ""
|
||||
|
||||
session, err := us.sessionStore.Save(session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
us.AddSessionToCache(session)
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (us *UserService) GetSession(token string) (*model.Session, error) {
|
||||
var session = us.sessionPool.Get().(*model.Session)
|
||||
if err := us.sessionCache.Get(token, session); err == nil {
|
||||
if us.metrics != nil {
|
||||
us.metrics.IncrementMemCacheHitCounterSession()
|
||||
}
|
||||
} else {
|
||||
if us.metrics != nil {
|
||||
us.metrics.IncrementMemCacheMissCounterSession()
|
||||
}
|
||||
}
|
||||
|
||||
if session.Id != "" {
|
||||
return session, nil
|
||||
}
|
||||
|
||||
return us.GetSessionContext(sqlstore.WithMaster(context.Background()), token)
|
||||
}
|
||||
|
||||
func (us *UserService) GetSessionContext(ctx context.Context, token string) (*model.Session, error) {
|
||||
return us.sessionStore.Get(ctx, token)
|
||||
}
|
||||
|
||||
func (us *UserService) GetSessions(userID string) ([]*model.Session, error) {
|
||||
return us.sessionStore.GetSessions(userID)
|
||||
}
|
||||
|
||||
func (us *UserService) AddSessionToCache(session *model.Session) {
|
||||
us.sessionCache.SetWithExpiry(session.Token, session, time.Duration(int64(*us.config().ServiceSettings.SessionCacheInMinutes))*time.Minute)
|
||||
}
|
||||
|
||||
func (us *UserService) SessionCacheLength() int {
|
||||
if l, err := us.sessionCache.Len(); err == nil {
|
||||
return l
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (us *UserService) ClearUserSessionCacheLocal(userID string) {
|
||||
if keys, err := us.sessionCache.Keys(); err == nil {
|
||||
var session *model.Session
|
||||
for _, key := range keys {
|
||||
if err := us.sessionCache.Get(key, &session); err == nil {
|
||||
if session.UserId == userID {
|
||||
us.sessionCache.Remove(key)
|
||||
if us.metrics != nil {
|
||||
us.metrics.IncrementMemCacheInvalidationCounterSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UserService) ClearAllUsersSessionCacheLocal() {
|
||||
us.sessionCache.Purge()
|
||||
}
|
||||
|
||||
func (us *UserService) ClearUserSessionCache(userID string) {
|
||||
us.ClearUserSessionCacheLocal(userID)
|
||||
|
||||
if us.cluster != nil {
|
||||
msg := &model.ClusterMessage{
|
||||
Event: model.ClusterEventClearSessionCacheForUser,
|
||||
SendType: model.ClusterSendReliable,
|
||||
Data: []byte(userID),
|
||||
}
|
||||
us.cluster.SendClusterMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UserService) ClearAllUsersSessionCache() {
|
||||
us.ClearAllUsersSessionCacheLocal()
|
||||
|
||||
if us.cluster != nil {
|
||||
msg := &model.ClusterMessage{
|
||||
Event: model.ClusterEventClearSessionCacheForAllUsers,
|
||||
SendType: model.ClusterSendReliable,
|
||||
}
|
||||
us.cluster.SendClusterMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UserService) GetSessionByID(sessionID string) (*model.Session, error) {
|
||||
return us.sessionStore.Get(context.Background(), sessionID)
|
||||
}
|
||||
|
||||
func (us *UserService) RevokeSessionsFromAllUsers() error {
|
||||
// revoke tokens before sessions so they can't be used to relogin
|
||||
nErr := us.oAuthStore.RemoveAllAccessData()
|
||||
if nErr != nil {
|
||||
return errors.Wrap(DeleteAllAccessDataError, nErr.Error())
|
||||
}
|
||||
err := us.sessionStore.RemoveAllSessions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
us.ClearAllUsersSessionCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (us *UserService) RevokeSessionsForDeviceId(userID string, deviceID string, currentSessionId string) error {
|
||||
sessions, err := us.sessionStore.GetSessions(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, session := range sessions {
|
||||
if session.DeviceId == deviceID && session.Id != currentSessionId {
|
||||
mlog.Debug("Revoking sessionId for userId. Re-login with the same device Id", mlog.String("session_id", session.Id), mlog.String("user_id", userID))
|
||||
if err := us.RevokeSession(session); err != nil {
|
||||
mlog.Warn("Could not revoke session for device", mlog.String("device_id", deviceID), mlog.Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (us *UserService) RevokeSession(session *model.Session) error {
|
||||
if session.IsOAuth {
|
||||
if err := us.RevokeAccessToken(session.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := us.sessionStore.Remove(session.Id); err != nil {
|
||||
return errors.Wrap(DeleteSessionError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
us.ClearUserSessionCache(session.UserId)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (us *UserService) RevokeAccessToken(token string) error {
|
||||
session, _ := us.GetSession(token)
|
||||
|
||||
defer us.ReturnSessionToPool(session)
|
||||
|
||||
schan := make(chan error, 1)
|
||||
go func() {
|
||||
schan <- us.sessionStore.Remove(token)
|
||||
close(schan)
|
||||
}()
|
||||
|
||||
if _, err := us.oAuthStore.GetAccessData(token); err != nil {
|
||||
return errors.Wrap(GetTokenError, err.Error())
|
||||
}
|
||||
|
||||
if err := us.oAuthStore.RemoveAccessData(token); err != nil {
|
||||
return errors.Wrap(DeleteTokenError, err.Error())
|
||||
}
|
||||
|
||||
if err := <-schan; err != nil {
|
||||
return errors.Wrap(DeleteSessionError, err.Error())
|
||||
}
|
||||
|
||||
if session != nil {
|
||||
us.ClearUserSessionCache(session.UserId)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSessionExpireInDays sets the session's expiry the specified number of days
|
||||
// relative to either the session creation date or the current time, depending
|
||||
// on the `ExtendSessionOnActivity` config setting.
|
||||
func (us *UserService) SetSessionExpireInDays(session *model.Session, days int) {
|
||||
if session.CreateAt == 0 || *us.config().ServiceSettings.ExtendSessionLengthWithActivity {
|
||||
session.ExpiresAt = model.GetMillis() + (1000 * 60 * 60 * 24 * int64(days))
|
||||
} else {
|
||||
session.ExpiresAt = session.CreateAt + (1000 * 60 * 60 * 24 * int64(days))
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UserService) UpdateSessionsIsGuest(userID string, isGuest bool) error {
|
||||
sessions, err := us.GetSessions(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, session := range sessions {
|
||||
session.AddProp(model.SessionPropIsGuest, fmt.Sprintf("%t", isGuest))
|
||||
err := us.sessionStore.UpdateProps(session)
|
||||
if err != nil {
|
||||
mlog.Warn("Unable to update isGuest session", mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
us.AddSessionToCache(session)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (us *UserService) RevokeAllSessions(userID string) error {
|
||||
sessions, err := us.sessionStore.GetSessions(userID)
|
||||
if err != nil {
|
||||
return errors.Wrap(GetSessionError, err.Error())
|
||||
}
|
||||
for _, session := range sessions {
|
||||
if session.IsOAuth {
|
||||
us.RevokeAccessToken(session.Token)
|
||||
} else {
|
||||
if err := us.sessionStore.Remove(session.Id); err != nil {
|
||||
return errors.Wrap(DeleteSessionError, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
us.ClearUserSessionCache(userID)
|
||||
|
||||
return nil
|
||||
}
|
||||
133
app/users/session_test.go
Обычный файл
133
app/users/session_test.go
Обычный файл
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
dayInMillis = 86400000
|
||||
grace = 5 * 1000
|
||||
thirtyDays = dayInMillis * 30
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
session := &model.Session{
|
||||
Id: model.NewId(),
|
||||
Token: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
}
|
||||
|
||||
session2 := &model.Session{
|
||||
Id: model.NewId(),
|
||||
Token: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
}
|
||||
|
||||
th.service.sessionCache.SetWithExpiry(session.Token, session, 5*time.Minute)
|
||||
th.service.sessionCache.SetWithExpiry(session2.Token, session2, 5*time.Minute)
|
||||
|
||||
keys, err := th.service.sessionCache.Keys()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, keys)
|
||||
|
||||
th.service.ClearUserSessionCache(session.UserId)
|
||||
|
||||
rkeys, err := th.service.sessionCache.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Lenf(t, rkeys, len(keys)-1, "should have one less: %d - %d != 1", len(keys), len(rkeys))
|
||||
require.NotEmpty(t, rkeys)
|
||||
|
||||
th.service.ClearAllUsersSessionCache()
|
||||
|
||||
rkeys, err = th.service.sessionCache.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, rkeys)
|
||||
}
|
||||
|
||||
func TestSetSessionExpireInDays(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
now := model.GetMillis()
|
||||
createAt := now - (dayInMillis * 20)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
extend bool
|
||||
create bool
|
||||
days int
|
||||
want int64
|
||||
}{
|
||||
{name: "zero days, extend", extend: true, create: true, days: 0, want: now},
|
||||
{name: "zero days, extend", extend: true, create: false, days: 0, want: now},
|
||||
{name: "zero days, no extend", extend: false, create: true, days: 0, want: createAt},
|
||||
{name: "zero days, no extend", extend: false, create: false, days: 0, want: now},
|
||||
{name: "thirty days, extend", extend: true, create: true, days: 30, want: now + thirtyDays},
|
||||
{name: "thirty days, extend", extend: true, create: false, days: 30, want: now + thirtyDays},
|
||||
{name: "thirty days, no extend", extend: false, create: true, days: 30, want: createAt + thirtyDays},
|
||||
{name: "thirty days, no extend", extend: false, create: false, days: 30, want: now + thirtyDays},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
th.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.ExtendSessionLengthWithActivity = tt.extend
|
||||
})
|
||||
var create int64
|
||||
if tt.create {
|
||||
create = createAt
|
||||
}
|
||||
|
||||
session := &model.Session{
|
||||
CreateAt: create,
|
||||
ExpiresAt: model.GetMillis() + dayInMillis,
|
||||
}
|
||||
th.service.SetSessionExpireInDays(session, tt.days)
|
||||
|
||||
// must be within 5 seconds of expected time.
|
||||
require.GreaterOrEqual(t, session.ExpiresAt, tt.want-grace)
|
||||
require.LessOrEqual(t, session.ExpiresAt, tt.want+grace)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthRevokeAccessToken(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
err := th.service.RevokeAccessToken(model.NewRandomString(16))
|
||||
require.Error(t, err, "Should have failed due to an incorrect token")
|
||||
|
||||
session := &model.Session{}
|
||||
session.CreateAt = model.GetMillis()
|
||||
session.UserId = model.NewId()
|
||||
session.Token = model.NewId()
|
||||
session.Roles = model.SystemUserRoleId
|
||||
th.service.SetSessionExpireInDays(session, 1)
|
||||
|
||||
session, _ = th.service.CreateSession(session)
|
||||
err = th.service.RevokeAccessToken(session.Token)
|
||||
require.Error(t, err, "Should have failed does not have an access token")
|
||||
|
||||
accessData := &model.AccessData{}
|
||||
accessData.Token = session.Token
|
||||
accessData.UserId = session.UserId
|
||||
accessData.RedirectUri = "http://example.com"
|
||||
accessData.ClientId = model.NewId()
|
||||
accessData.ExpiresAt = session.ExpiresAt
|
||||
|
||||
_, nErr := th.service.oAuthStore.SaveAccessData(accessData)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
err = th.service.RevokeAccessToken(accessData.Token)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
256
app/users/users.go
Обычный файл
256
app/users/users.go
Обычный файл
@@ -0,0 +1,256 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mfa"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type UserCreateOptions struct {
|
||||
Guest bool
|
||||
FromImport bool
|
||||
}
|
||||
|
||||
// CreateUser creates a user
|
||||
func (us *UserService) CreateUser(user *model.User, opts UserCreateOptions) (*model.User, error) {
|
||||
if opts.FromImport {
|
||||
return us.createUser(user)
|
||||
}
|
||||
|
||||
user.Roles = model.SystemUserRoleId
|
||||
if opts.Guest {
|
||||
user.Roles = model.SystemGuestRoleId
|
||||
}
|
||||
|
||||
if !user.IsLDAPUser() && !user.IsSAMLUser() && !user.IsGuest() && !CheckUserDomain(user, *us.config().TeamSettings.RestrictCreationToDomains) {
|
||||
return nil, AcceptedDomainError
|
||||
}
|
||||
|
||||
if !user.IsLDAPUser() && !user.IsSAMLUser() && user.IsGuest() && !CheckUserDomain(user, *us.config().GuestAccountsSettings.RestrictCreationToDomains) {
|
||||
return nil, AcceptedDomainError
|
||||
}
|
||||
|
||||
// Below is a special case where the first user in the entire
|
||||
// system is granted the system_admin role
|
||||
if ok, err := us.store.IsEmpty(true); err != nil {
|
||||
return nil, errors.Wrap(UserStoreIsEmptyError, err.Error())
|
||||
} else if ok {
|
||||
user.Roles = model.SystemAdminRoleId + " " + model.SystemUserRoleId
|
||||
}
|
||||
|
||||
if _, ok := i18n.GetSupportedLocales()[user.Locale]; !ok {
|
||||
user.Locale = *us.config().LocalizationSettings.DefaultClientLocale
|
||||
}
|
||||
|
||||
return us.createUser(user)
|
||||
}
|
||||
|
||||
func (us *UserService) createUser(user *model.User) (*model.User, error) {
|
||||
user.MakeNonNil()
|
||||
|
||||
if err := us.isPasswordValid(user.Password); user.AuthService == "" && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ruser, err := us.store.Save(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.EmailVerified {
|
||||
if err := us.verifyUserEmail(ruser.Id, user.Email); err != nil {
|
||||
mlog.Warn("Failed to set email verified", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether to send the created user a welcome email
|
||||
ruser.DisableWelcomeEmail = user.DisableWelcomeEmail
|
||||
ruser.Sanitize(map[string]bool{})
|
||||
|
||||
return ruser, nil
|
||||
}
|
||||
|
||||
func (us *UserService) verifyUserEmail(userID, email string) error {
|
||||
if _, err := us.store.VerifyEmail(userID, email); err != nil {
|
||||
return VerifyUserError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (us *UserService) GetUser(userID string) (*model.User, error) {
|
||||
return us.store.Get(context.Background(), userID)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUserByUsername(username string) (*model.User, error) {
|
||||
return us.store.GetByUsername(username)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUserByEmail(email string) (*model.User, error) {
|
||||
return us.store.GetByEmail(email)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUserByAuth(authData *string, authService string) (*model.User, error) {
|
||||
return us.store.GetByAuth(authData, authService)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsers(options *model.UserGetOptions) ([]*model.User, error) {
|
||||
return us.store.GetAllProfiles(options)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersByUsernames(usernames []string, options *model.UserGetOptions) ([]*model.User, error) {
|
||||
return us.store.GetProfilesByUsernames(usernames, options.ViewRestrictions)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, error) {
|
||||
users, err := us.GetUsers(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return us.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersEtag(restrictionsHash string) string {
|
||||
return fmt.Sprintf("%v.%v.%v.%v", us.store.GetEtagForAllProfiles(), us.config().PrivacySettings.ShowFullName, us.config().PrivacySettings.ShowEmailAddress, restrictionsHash)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersByIds(userIDs []string, options *store.UserGetByIdsOpts) ([]*model.User, error) {
|
||||
allowFromCache := options.ViewRestrictions == nil
|
||||
|
||||
users, err := us.store.GetProfileByIds(context.Background(), userIDs, options, allowFromCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return us.sanitizeProfiles(users, options.IsAdmin), nil
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersInTeam(options *model.UserGetOptions) ([]*model.User, error) {
|
||||
return us.store.GetProfiles(options)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersNotInTeam(teamID string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error) {
|
||||
return us.store.GetProfilesNotInTeam(teamID, groupConstrained, offset, limit, viewRestrictions)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersInTeamPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, error) {
|
||||
users, err := us.GetUsersInTeam(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return us.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersNotInTeamPage(teamID string, groupConstrained bool, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error) {
|
||||
users, err := us.GetUsersNotInTeam(teamID, groupConstrained, page*perPage, perPage, viewRestrictions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return us.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersInTeamEtag(teamID string, restrictionsHash string) string {
|
||||
return fmt.Sprintf("%v.%v.%v.%v", us.store.GetEtagForProfiles(teamID), us.config().PrivacySettings.ShowFullName, us.config().PrivacySettings.ShowEmailAddress, restrictionsHash)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersNotInTeamEtag(teamID string, restrictionsHash string) string {
|
||||
return fmt.Sprintf("%v.%v.%v.%v", us.store.GetEtagForProfilesNotInTeam(teamID), us.config().PrivacySettings.ShowFullName, us.config().PrivacySettings.ShowEmailAddress, restrictionsHash)
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersWithoutTeamPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, error) {
|
||||
users, err := us.GetUsersWithoutTeam(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return us.sanitizeProfiles(users, asAdmin), nil
|
||||
}
|
||||
|
||||
func (us *UserService) GetUsersWithoutTeam(options *model.UserGetOptions) ([]*model.User, error) {
|
||||
users, err := us.store.GetProfilesWithoutTeam(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (us *UserService) UpdateUser(user *model.User, allowRoleUpdate bool) (*model.UserUpdate, error) {
|
||||
return us.store.Update(user, allowRoleUpdate)
|
||||
}
|
||||
|
||||
func (us *UserService) UpdateUserNotifyProps(userID string, props map[string]string) error {
|
||||
return us.store.UpdateNotifyProps(userID, props)
|
||||
}
|
||||
|
||||
func (us *UserService) DeactivateAllGuests() ([]string, error) {
|
||||
users, err := us.store.DeactivateGuests()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, userID := range users {
|
||||
if err := us.RevokeAllSessions(userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (us *UserService) InvalidateCacheForUser(userID string) {
|
||||
us.store.InvalidateProfilesInChannelCacheByUser(userID)
|
||||
us.store.InvalidateProfileCacheForUser(userID)
|
||||
|
||||
if us.cluster != nil {
|
||||
msg := &model.ClusterMessage{
|
||||
Event: model.ClusterEventInvalidateCacheForUser,
|
||||
SendType: model.ClusterSendBestEffort,
|
||||
Data: []byte(userID),
|
||||
}
|
||||
us.cluster.SendClusterMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UserService) GenerateMfaSecret(user *model.User) (*model.MfaSecret, error) {
|
||||
secret, img, err := mfa.New(us.store).GenerateSecret(*us.config().ServiceSettings.SiteURL, user.Email, user.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure the old secret is not cached on any cluster nodes.
|
||||
us.InvalidateCacheForUser(user.Id)
|
||||
|
||||
mfaSecret := &model.MfaSecret{Secret: secret, QRCode: base64.StdEncoding.EncodeToString(img)}
|
||||
return mfaSecret, nil
|
||||
}
|
||||
|
||||
func (us *UserService) ActivateMfa(user *model.User, token string) error {
|
||||
return mfa.New(us.store).Activate(user.MfaSecret, user.Id, token)
|
||||
}
|
||||
|
||||
func (us *UserService) DeactivateMfa(user *model.User) error {
|
||||
return mfa.New(us.store).Deactivate(user.Id)
|
||||
}
|
||||
|
||||
func (us *UserService) PromoteGuestToUser(user *model.User) error {
|
||||
return us.store.PromoteGuestToUser(user.Id)
|
||||
}
|
||||
|
||||
func (us *UserService) DemoteUserToGuest(user *model.User) (*model.User, error) {
|
||||
return us.store.DemoteUserToGuest(user.Id)
|
||||
}
|
||||
89
app/users/users_test.go
Обычный файл
89
app/users/users_test.go
Обычный файл
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsUsernameTaken(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user := th.BasicUser
|
||||
taken := th.service.IsUsernameTaken(user.Username)
|
||||
|
||||
if !taken {
|
||||
t.Logf("the username '%v' should be taken", user.Username)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
newUsername := "randomUsername"
|
||||
taken = th.service.IsUsernameTaken(newUsername)
|
||||
|
||||
if taken {
|
||||
t.Logf("the username '%v' should not be taken", newUsername)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstUserPromoted(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
user, err := th.service.CreateUser(&model.User{
|
||||
Username: model.NewId(),
|
||||
Password: model.NewId(),
|
||||
Email: "user@example.com",
|
||||
}, UserCreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, user)
|
||||
|
||||
require.Equal(t, model.SystemAdminRoleId+" "+model.SystemUserRoleId, user.Roles)
|
||||
|
||||
user2, err := th.service.CreateUser(&model.User{
|
||||
Username: model.NewId(),
|
||||
Password: model.NewId(),
|
||||
Email: "user2@example.com",
|
||||
}, UserCreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, user2)
|
||||
|
||||
require.Equal(t, model.SystemUserRoleId, user2.Roles)
|
||||
|
||||
th.dbStore.User().PermanentDelete(user.Id)
|
||||
|
||||
b := &model.Bot{
|
||||
UserId: user2.Id,
|
||||
OwnerId: model.NewId(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
|
||||
_, err = th.dbStore.Bot().Save(b)
|
||||
require.NoError(t, err)
|
||||
|
||||
user3, err := th.service.CreateUser(&model.User{
|
||||
Username: model.NewId(),
|
||||
Password: model.NewId(),
|
||||
Email: "user3@example.com",
|
||||
}, UserCreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, user3)
|
||||
|
||||
require.Equal(t, model.SystemAdminRoleId+" "+model.SystemUserRoleId, user3.Roles)
|
||||
|
||||
user4, err := th.service.CreateUser(&model.User{
|
||||
Username: model.NewId(),
|
||||
Password: model.NewId(),
|
||||
Email: "user4@example.com",
|
||||
}, UserCreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, user4)
|
||||
|
||||
require.Equal(t, model.SystemUserRoleId, user4.Roles)
|
||||
}
|
||||
94
app/users/utils.go
Обычный файл
94
app/users/utils.go
Обычный файл
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func maxInt(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (us *UserService) IsFirstUserAccount() bool {
|
||||
cachedSessions, err := us.sessionCache.Len()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if cachedSessions == 0 {
|
||||
count, err := us.store.Count(model.UserCountOptions{IncludeDeleted: true})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if count <= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckUserDomain checks that a user's email domain matches a list of space-delimited domains as a string.
|
||||
func CheckUserDomain(user *model.User, domains string) bool {
|
||||
return CheckEmailDomain(user.Email, domains)
|
||||
}
|
||||
|
||||
// CheckEmailDomain checks that an email domain matches a list of space-delimited domains as a string.
|
||||
func CheckEmailDomain(email string, domains string) bool {
|
||||
if domains == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
domainArray := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(strings.Replace(domains, "@", " ", -1), ",", " ", -1))))
|
||||
|
||||
for _, d := range domainArray {
|
||||
if strings.HasSuffix(strings.ToLower(email), "@"+d) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (us *UserService) sanitizeProfiles(users []*model.User, asAdmin bool) []*model.User {
|
||||
for _, u := range users {
|
||||
us.SanitizeProfile(u, asAdmin)
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func (us *UserService) SanitizeProfile(user *model.User, asAdmin bool) {
|
||||
options := us.GetSanitizeOptions(asAdmin)
|
||||
|
||||
user.SanitizeProfile(options)
|
||||
}
|
||||
|
||||
func (us *UserService) GetSanitizeOptions(asAdmin bool) map[string]bool {
|
||||
options := us.config().GetSanitizeOptions()
|
||||
if asAdmin {
|
||||
options["email"] = true
|
||||
options["fullname"] = true
|
||||
options["authservice"] = true
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// IsUsernameTaken checks if the username is already used by another user. Return false if the username is invalid.
|
||||
func (us *UserService) IsUsernameTaken(name string) bool {
|
||||
if !model.IsValidUsername(name) {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := us.store.GetByUsername(name); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Ссылка в новой задаче
Block a user