MM24459: Implement ldap picture sync (#14540)

* implement ldap picture sync

* add testing timeout, and no change

* Revert "add testing timeout, and no change"

This reverts commit 765621a7290074e5664c4ca2a2843c84e01f4cf1.

* update app-layer

* updates from code review

* update app-layers

* remove debug statements

Co-authored-by: mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Scott Bishel
2020-05-19 08:25:52 -06:00
коммит произвёл GitHub
родитель d7cb890f34
Коммит e8081b7a0f
8 изменённых файлов: 167 добавлений и 13 удалений

Просмотреть файл

@@ -267,6 +267,8 @@ type AppIface interface {
// This function deviates from other authorization checks in returning an error instead of just
// a boolean, allowing the permission failure to be exposed with more granularity.
SessionHasPermissionToManageBot(session model.Session, botUserId string) *model.AppError
// SessionIsRegistered determines if a specific session has been registered
SessionIsRegistered(session model.Session) bool
// SetBotIconImage sets LHS icon for a bot.
SetBotIconImage(botUserId string, file io.ReadSeeker) *model.AppError
// SetBotIconImageFromMultiPartFile sets LHS icon for a bot.
@@ -350,6 +352,7 @@ type AppIface interface {
AddUserToTeamByInviteId(inviteId string, userId string) (*model.Team, *model.AppError)
AddUserToTeamByTeamId(teamId string, user *model.User) *model.AppError
AddUserToTeamByToken(userId string, tokenId string) (*model.Team, *model.AppError)
AdjustImage(file io.Reader) (*bytes.Buffer, *model.AppError)
AllowOAuthAppAccessToUser(userId string, authRequest *model.AuthorizeRequest) (string, *model.AppError)
AsymmetricSigningKey() *ecdsa.PrivateKey
AttachDeviceId(sessionId string, deviceId string, expiresAt int64) *model.AppError

Просмотреть файл

@@ -165,6 +165,12 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
a.SetSession(session)
if user.AuthService == model.USER_AUTH_SERVICE_LDAP && a.Ldap() != nil {
a.Srv().Go(func() {
a.Ldap().UpdateProfilePictureIfNecessary(user, session)
})
}
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
a.Srv().Go(func() {
pluginContext := a.PluginContext()

Просмотреть файл

@@ -541,6 +541,28 @@ func (a *OpenTracingAppLayer) AddUserToTeamByToken(userId string, tokenId string
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) AdjustImage(file io.Reader) (*bytes.Buffer, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AdjustImage")
a.ctx = newCtx
a.app.Srv().Store.SetContext(newCtx)
defer func() {
a.app.Srv().Store.SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.AdjustImage(file)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) AllowOAuthAppAccessToUser(userId string, authRequest *model.AuthorizeRequest) (string, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AllowOAuthAppAccessToUser")
@@ -12768,6 +12790,23 @@ func (a *OpenTracingAppLayer) SessionHasPermissionToUserOrBot(session model.Sess
return resultVar0
}
func (a *OpenTracingAppLayer) SessionIsRegistered(session model.Session) bool {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SessionIsRegistered")
a.ctx = newCtx
a.app.Srv().Store.SetContext(newCtx)
defer func() {
a.app.Srv().Store.SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0 := a.app.SessionIsRegistered(session)
return resultVar0
}
func (a *OpenTracingAppLayer) SetActiveChannel(userId string, channelId string) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SetActiveChannel")

Просмотреть файл

@@ -855,12 +855,11 @@ func (a *App) SetProfileImageFromMultiPartFile(userId string, file multipart.Fil
return a.SetProfileImageFromFile(userId, file)
}
func (a *App) SetProfileImageFromFile(userId string, file io.Reader) *model.AppError {
func (a *App) AdjustImage(file io.Reader) (*bytes.Buffer, *model.AppError) {
// Decode image into Image object
img, _, err := image.Decode(file)
if err != nil {
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.decode.app_error", nil, err.Error(), http.StatusBadRequest)
return nil, model.NewAppError("SetProfileImage", "api.user.upload_profile_user.decode.app_error", nil, err.Error(), http.StatusBadRequest)
}
orientation, _ := getImageOrientation(file)
@@ -873,9 +872,17 @@ func (a *App) SetProfileImageFromFile(userId string, file io.Reader) *model.AppE
buf := new(bytes.Buffer)
err = png.Encode(buf, img)
if err != nil {
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.encode.app_error", nil, err.Error(), http.StatusInternalServerError)
return nil, model.NewAppError("SetProfileImage", "api.user.upload_profile_user.encode.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return buf, nil
}
func (a *App) SetProfileImageFromFile(userId string, file io.Reader) *model.AppError {
buf, err := a.AdjustImage(file)
if err != nil {
return err
}
path := "users/" + userId + "/profile.png"
if _, err := a.WriteFile(buf, path); err != nil {

Просмотреть файл

@@ -18,6 +18,7 @@ import (
"github.com/mattermost/mattermost-server/v5/einterfaces"
"github.com/mattermost/mattermost-server/v5/model"
oauthgitlab "github.com/mattermost/mattermost-server/v5/model/gitlab"
"github.com/mattermost/mattermost-server/v5/utils/testutils"
)
func TestIsUsernameTaken(t *testing.T) {
@@ -121,6 +122,31 @@ func TestSetDefaultProfileImage(t *testing.T) {
assert.Equal(t, int64(0), user.LastPictureUpdate)
}
func TestAdjustProfileImage(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
_, err := th.App.AdjustImage(bytes.NewReader([]byte{}))
require.Error(t, err)
// test image isn't the correct dimensions
// it should be adjusted
testjpg, error := testutils.ReadTestFile("testjpg.jpg")
require.Nil(t, error)
adjusted, err := th.App.AdjustImage(bytes.NewReader(testjpg))
require.Nil(t, err)
assert.True(t, adjusted.Len() > 0)
assert.NotEqual(t, testjpg, adjusted)
// default image should require adjustement
user := th.BasicUser
image, err := th.App.GetDefaultProfileImage(user)
require.Nil(t, err)
image2, err := th.App.AdjustImage(bytes.NewReader(image))
require.Nil(t, err)
assert.Equal(t, image, image2.Bytes())
}
func TestUpdateUserToRestrictedDomain(t *testing.T) {
th := Setup(t)
defer th.TearDown()

Просмотреть файл

@@ -29,6 +29,12 @@ type webConnDirectMessage struct {
msg model.WebSocketMessage
}
type webConnSessionMessage struct {
userId string
sessionToken string
isRegistered chan bool
}
// Hub is the central place to manage all websocket connections in the server.
// It handles different websocket events and sending messages to individual
// user connections.
@@ -47,20 +53,22 @@ type Hub struct {
activity chan *webConnActivityMessage
directMsg chan *webConnDirectMessage
explicitStop bool
checkRegistered chan *webConnSessionMessage
}
// NewWebHub creates a new Hub.
func (a *App) NewWebHub() *Hub {
return &Hub{
app: a,
register: make(chan *WebConn),
unregister: make(chan *WebConn),
broadcast: make(chan *model.WebSocketEvent, broadcastQueueSize),
stop: make(chan struct{}),
didStop: make(chan struct{}),
invalidateUser: make(chan string),
activity: make(chan *webConnActivityMessage),
directMsg: make(chan *webConnDirectMessage),
app: a,
register: make(chan *WebConn),
unregister: make(chan *WebConn),
broadcast: make(chan *model.WebSocketEvent, broadcastQueueSize),
stop: make(chan struct{}),
didStop: make(chan struct{}),
invalidateUser: make(chan string),
activity: make(chan *webConnActivityMessage),
directMsg: make(chan *webConnDirectMessage),
checkRegistered: make(chan *webConnSessionMessage),
}
}
@@ -289,6 +297,15 @@ func (a *App) UpdateWebConnUserActivity(session model.Session, activityAt int64)
}
}
// SessionIsRegistered determines if a specific session has been registered
func (a *App) SessionIsRegistered(session model.Session) bool {
hub := a.GetHubForUserId(session.UserId)
if hub != nil {
return hub.IsRegistered(session.UserId, session.Token)
}
return false
}
// Register registers a connection to the hub.
func (h *Hub) Register(webConn *WebConn) {
select {
@@ -305,6 +322,21 @@ func (h *Hub) Unregister(webConn *WebConn) {
}
}
// Determines if a user's session is registered a connection from the hub.
func (h *Hub) IsRegistered(userId, sessionToken string) bool {
ws := &webConnSessionMessage{
userId: userId,
sessionToken: sessionToken,
isRegistered: make(chan bool),
}
select {
case h.checkRegistered <- ws:
return <-ws.isRegistered
case <-h.stop:
}
return false
}
// Broadcast broadcasts the message to all connections in the hub.
func (h *Hub) Broadcast(message *model.WebSocketEvent) {
// XXX: The hub nil check is because of the way we setup our tests. We call `app.NewServer()`
@@ -375,6 +407,15 @@ func (h *Hub) Start() {
for {
select {
case webSessionMessage := <-h.checkRegistered:
conns := connIndex.ForUser(webSessionMessage.userId)
var isRegistered bool
for _, item := range conns {
if item.sessionToken.Load().(string) == webSessionMessage.sessionToken {
isRegistered = true
}
}
webSessionMessage.isRegistered <- isRegistered
case webConn := <-h.register:
connIndex.Add(webConn)
atomic.StoreInt64(&h.connectionCount, int64(len(connIndex.All())))

Просмотреть файл

@@ -12,13 +12,16 @@ import (
"github.com/gorilla/websocket"
goi18n "github.com/mattermost/go-i18n/i18n"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
)
func dummyWebsocketHandler(t *testing.T) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
mlog.Debug("dummyWebsocketHandler")
upgrader := &websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
@@ -106,3 +109,31 @@ func TestHubStopRaceCondition(t *testing.T) {
require.FailNow(t, "hub call did not return within 15 seconds after stop")
}
}
func TestHubIsRegistered(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
s := httptest.NewServer(dummyWebsocketHandler(t))
defer s.Close()
th.App.HubStart()
wc1 := registerDummyWebConn(t, th.App, s.Listener.Addr(), th.BasicUser.Id)
wc2 := registerDummyWebConn(t, th.App, s.Listener.Addr(), th.BasicUser.Id)
wc3 := registerDummyWebConn(t, th.App, s.Listener.Addr(), th.BasicUser.Id)
defer wc1.Close()
defer wc2.Close()
defer wc3.Close()
session1 := wc1.session.Load().(*model.Session)
assert.True(t, th.App.SessionIsRegistered(*session1))
assert.True(t, th.App.SessionIsRegistered(*wc2.session.Load().(*model.Session)))
assert.True(t, th.App.SessionIsRegistered(*wc3.session.Load().(*model.Session)))
session4, appErr := th.App.CreateSession(&model.Session{
UserId: th.BasicUser2.Id,
})
require.Nil(t, appErr)
assert.False(t, th.App.SessionIsRegistered(*session4))
}