Merge pull request #1993 from mattermost/PLT-1586
PLT-1586 adding LDAP/OAuth to iOS
Этот коммит содержится в:
45
api/user.go
45
api/user.go
@@ -48,6 +48,7 @@ func InitUser(r *mux.Router) {
|
|||||||
sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST")
|
sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST")
|
||||||
sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST")
|
sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST")
|
||||||
sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
|
sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
|
||||||
|
sr.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST")
|
||||||
sr.Handle("/switch_to_sso", ApiAppHandler(switchToSSO)).Methods("POST")
|
sr.Handle("/switch_to_sso", ApiAppHandler(switchToSSO)).Methods("POST")
|
||||||
sr.Handle("/switch_to_email", ApiUserRequired(switchToEmail)).Methods("POST")
|
sr.Handle("/switch_to_email", ApiUserRequired(switchToEmail)).Methods("POST")
|
||||||
|
|
||||||
@@ -546,7 +547,6 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthWebInDays)
|
session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthWebInDays)
|
||||||
}
|
}
|
||||||
@@ -718,6 +718,49 @@ func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte(model.MapToJson(props)))
|
w.Write([]byte(model.MapToJson(props)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
props := model.MapFromJson(r.Body)
|
||||||
|
|
||||||
|
deviceId := props["device_id"]
|
||||||
|
if len(deviceId) == 0 {
|
||||||
|
c.SetInvalidParam("attachDevice", "deviceId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(strings.HasPrefix(deviceId, model.PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(deviceId, model.PUSH_NOTIFY_ANDROID+":")) {
|
||||||
|
c.SetInvalidParam("attachDevice", "deviceId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A special case where we logout of all other sessions with the same Id
|
||||||
|
if result := <-Srv.Store.Session().GetSessions(c.Session.UserId); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
c.Err.StatusCode = http.StatusForbidden
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
sessions := result.Data.([]*model.Session)
|
||||||
|
for _, session := range sessions {
|
||||||
|
if session.DeviceId == deviceId && session.Id != c.Session.Id {
|
||||||
|
l4g.Debug(utils.T("api.user.login.revoking.app_error"), session.Id, c.Session.UserId)
|
||||||
|
RevokeSessionById(c, session.Id)
|
||||||
|
if c.Err != nil {
|
||||||
|
c.LogError(c.Err)
|
||||||
|
c.Err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCache.Remove(c.Session.Token)
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Session().UpdateDeviceId(c.Session.Id, deviceId); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(deviceId))
|
||||||
|
}
|
||||||
|
|
||||||
func RevokeSessionById(c *Context, sessionId string) {
|
func RevokeSessionById(c *Context, sessionId string) {
|
||||||
if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil {
|
if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil {
|
||||||
c.Err = result.Err
|
c.Err = result.Err
|
||||||
|
|||||||
@@ -734,6 +734,34 @@ func TestUserUpdateRoles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserUpdateDeviceId(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||||
|
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||||
|
|
||||||
|
user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user.Id))
|
||||||
|
|
||||||
|
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||||
|
deviceId := model.PUSH_NOTIFY_APPLE + ":1234567890"
|
||||||
|
|
||||||
|
if _, err := Client.AttachDeviceId(deviceId); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Session().GetSessions(user.Id); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
sessions := result.Data.([]*model.Session)
|
||||||
|
|
||||||
|
if sessions[0].DeviceId != deviceId {
|
||||||
|
t.Fatal("Missing device Id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUserUpdateActive(t *testing.T) {
|
func TestUserUpdateActive(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
|
|||||||
@@ -2771,6 +2771,10 @@
|
|||||||
"id": "store.sql_session.update_roles.app_error",
|
"id": "store.sql_session.update_roles.app_error",
|
||||||
"translation": "We couldn't update the roles"
|
"translation": "We couldn't update the roles"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "store.sql_session.update_device_id.app_error",
|
||||||
|
"translation": "We couldn't update the device id"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "store.sql_system.get.app_error",
|
"id": "store.sql_system.get.app_error",
|
||||||
"translation": "We encountered an error finding the system properties"
|
"translation": "We encountered an error finding the system properties"
|
||||||
|
|||||||
@@ -775,6 +775,17 @@ func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) AttachDeviceId(deviceId string) (*Result, *AppError) {
|
||||||
|
data := make(map[string]string)
|
||||||
|
data["device_id"] = deviceId
|
||||||
|
if r, err := c.DoApiPost("/users/attach_device", MapToJson(data)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) {
|
func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) {
|
||||||
data := make(map[string]string)
|
data := make(map[string]string)
|
||||||
data["user_id"] = userId
|
data["user_id"] = userId
|
||||||
|
|||||||
@@ -232,3 +232,21 @@ func (me SqlSessionStore) UpdateRoles(userId, roles string) StoreChannel {
|
|||||||
|
|
||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (me SqlSessionStore) UpdateDeviceId(id, deviceId string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
if _, err := me.GetMaster().Exec("UPDATE Sessions SET DeviceId = :DeviceId WHERE Id = :Id", map[string]interface{}{"DeviceId": deviceId, "Id": id}); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlSessionStore.UpdateDeviceId", "store.sql_session.update_device_id.app_error", nil, "")
|
||||||
|
} else {
|
||||||
|
result.Data = deviceId
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|||||||
@@ -157,6 +157,28 @@ func TestSessionRemoveToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionUpdateDeviceId(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
s1 := model.Session{}
|
||||||
|
s1.UserId = model.NewId()
|
||||||
|
s1.TeamId = model.NewId()
|
||||||
|
Must(store.Session().Save(&s1))
|
||||||
|
|
||||||
|
if rs1 := (<-store.Session().UpdateDeviceId(s1.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs1.Err != nil {
|
||||||
|
t.Fatal(rs1.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s2 := model.Session{}
|
||||||
|
s2.UserId = model.NewId()
|
||||||
|
s2.TeamId = model.NewId()
|
||||||
|
Must(store.Session().Save(&s2))
|
||||||
|
|
||||||
|
if rs2 := (<-store.Session().UpdateDeviceId(s2.Id, model.PUSH_NOTIFY_APPLE+":1234567890")); rs2.Err != nil {
|
||||||
|
t.Fatal(rs2.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSessionStoreUpdateLastActivityAt(t *testing.T) {
|
func TestSessionStoreUpdateLastActivityAt(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ type SessionStore interface {
|
|||||||
PermanentDeleteSessionsByUser(teamId string) StoreChannel
|
PermanentDeleteSessionsByUser(teamId string) StoreChannel
|
||||||
UpdateLastActivityAt(sessionId string, time int64) StoreChannel
|
UpdateLastActivityAt(sessionId string, time int64) StoreChannel
|
||||||
UpdateRoles(userId string, roles string) StoreChannel
|
UpdateRoles(userId string, roles string) StoreChannel
|
||||||
|
UpdateDeviceId(id string, deviceId string) StoreChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuditStore interface {
|
type AuditStore interface {
|
||||||
|
|||||||
@@ -100,12 +100,15 @@ export default class ActivityLogModal extends React.Component {
|
|||||||
|
|
||||||
if (currentSession.props.platform === 'Windows') {
|
if (currentSession.props.platform === 'Windows') {
|
||||||
devicePicture = 'fa fa-windows';
|
devicePicture = 'fa fa-windows';
|
||||||
|
} else if (currentSession.device_id.indexOf('apple:') === 0) {
|
||||||
|
devicePicture = 'fa fa-apple';
|
||||||
|
devicePlatform = 'iPhone Native App';
|
||||||
|
} else if (currentSession.device_id.indexOf('android:') === 0) {
|
||||||
|
devicePlatform = 'Android Native App';
|
||||||
|
devicePicture = 'fa fa-android';
|
||||||
} else if (currentSession.props.platform === 'Macintosh' ||
|
} else if (currentSession.props.platform === 'Macintosh' ||
|
||||||
currentSession.props.platform === 'iPhone') {
|
currentSession.props.platform === 'iPhone') {
|
||||||
devicePicture = 'fa fa-apple';
|
devicePicture = 'fa fa-apple';
|
||||||
} else if (currentSession.props.platform.browser.indexOf('Mattermost/') === 0) {
|
|
||||||
devicePicture = 'fa fa-apple';
|
|
||||||
devicePlatform = 'iPhone';
|
|
||||||
} else if (currentSession.props.platform === 'Linux') {
|
} else if (currentSession.props.platform === 'Linux') {
|
||||||
if (currentSession.props.os.indexOf('Android') >= 0) {
|
if (currentSession.props.os.indexOf('Android') >= 0) {
|
||||||
devicePlatform = 'Android';
|
devicePlatform = 'Android';
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export default class Login extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let teamSignUp = null;
|
let teamSignUp = null;
|
||||||
if (global.window.mm_config.EnableTeamCreation === 'true') {
|
if (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp()) {
|
||||||
teamSignUp = (
|
teamSignUp = (
|
||||||
<div className='margin--extra'>
|
<div className='margin--extra'>
|
||||||
<a
|
<a
|
||||||
@@ -137,6 +137,21 @@ export default class Login extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let findTeams = null;
|
||||||
|
if (!Utils.isMobileApp()) {
|
||||||
|
findTeams = (
|
||||||
|
<div className='form-group margin--extra form-group--small'>
|
||||||
|
<span>
|
||||||
|
<a href='/find_team'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='login.find_teams'
|
||||||
|
defaultMessage='Find your other teams'
|
||||||
|
/>
|
||||||
|
</a></span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='signup-team__container'>
|
<div className='signup-team__container'>
|
||||||
<h5 className='margin--less'>{'Sign in to:'}</h5>
|
<h5 className='margin--less'>{'Sign in to:'}</h5>
|
||||||
@@ -147,15 +162,7 @@ export default class Login extends React.Component {
|
|||||||
{emailSignup}
|
{emailSignup}
|
||||||
{ldapLogin}
|
{ldapLogin}
|
||||||
{userSignUp}
|
{userSignUp}
|
||||||
<div className='form-group margin--extra form-group--small'>
|
{findTeams}
|
||||||
<span>
|
|
||||||
<a href='/find_team'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='login.find_teams'
|
|
||||||
defaultMessage='Find your other teams'
|
|
||||||
/>
|
|
||||||
</a></span>
|
|
||||||
</div>
|
|
||||||
{forgotPassword}
|
{forgotPassword}
|
||||||
{teamSignUp}
|
{teamSignUp}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,8 +5,22 @@
|
|||||||
<body class="white">
|
<body class="white">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="inner__wrap">
|
<div class="inner__wrap">
|
||||||
<div class="row content" id="claim"></div>
|
<div class="row content">
|
||||||
</div>
|
<div class="signup-header">
|
||||||
|
<a href="/{{.Props.TeamName}}">{{.Props.TeamDisplayName}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="signup-team__container">
|
||||||
|
<img class="signup-team-logo" src="/static/images/logo.png" />
|
||||||
|
<div id="claim"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-push"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row footer">
|
||||||
|
{{template "footer" . }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
window.setup_claim_account_page({{ .Props }});
|
window.setup_claim_account_page({{ .Props }});
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="inner__wrap">
|
<div class="inner__wrap">
|
||||||
<div class="row content">
|
<div class="row content">
|
||||||
|
<div class="signup-header">
|
||||||
|
<a href="/">{{ .ClientCfg.SiteName }}</a>
|
||||||
|
</div>
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="docs__page" id="docs"></div>
|
<div class="docs__page" id="docs"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="inner__wrap">
|
<div class="inner__wrap">
|
||||||
<div class="row content">
|
<div class="row content">
|
||||||
|
<div class="signup-header">
|
||||||
|
<a href="/">{{ .ClientCfg.SiteName }}</a>
|
||||||
|
</div>
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="signup-team__container">
|
<div class="signup-team__container">
|
||||||
<img class="signup-team-logo" src="/static/images/logo.png" />
|
<img class="signup-team-logo" src="/static/images/logo.png" />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div class="inner__wrap">
|
<div class="inner__wrap">
|
||||||
<div class="row content">
|
<div class="row content">
|
||||||
<div class="signup-header">
|
<div class="signup-header">
|
||||||
{{.Props.TeamDisplayName}}
|
<a href="/">{{ .ClientCfg.SiteName }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div id="login"></div>
|
<div id="login"></div>
|
||||||
|
|||||||
@@ -5,7 +5,21 @@
|
|||||||
<body class="white">
|
<body class="white">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="inner__wrap">
|
<div class="inner__wrap">
|
||||||
<div class="row content" id="reset"></div>
|
<div class="row content">
|
||||||
|
<div class="signup-header">
|
||||||
|
<a href="/{{.Props.TeamName}}">{{.Props.TeamDisplayName}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="signup-team__container">
|
||||||
|
<img class="signup-team-logo" src="/static/images/logo.png" />
|
||||||
|
<div id="reset"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-push"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row footer">
|
||||||
|
{{template "footer" . }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -6,7 +6,16 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="inner__wrap">
|
<div class="inner__wrap">
|
||||||
<div class="row content">
|
<div class="row content">
|
||||||
<div id="verify"></div>
|
<div class="signup-header">
|
||||||
|
<a href="/{{.Props.TeamName}}">{{.Props.TeamDisplayName}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="signup-team__container">
|
||||||
|
<img class="signup-team-logo" src="/static/images/logo.png" />
|
||||||
|
<div id="verify"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-push"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row footer">
|
<div class="row footer">
|
||||||
{{template "footer" . }}
|
{{template "footer" . }}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
{{define "welcome"}}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
{{template "head" . }}
|
|
||||||
<body>
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="inner__wrap">
|
|
||||||
<div class="row header">
|
|
||||||
<div id="navbar"></div>
|
|
||||||
</div>
|
|
||||||
<div class="row main">
|
|
||||||
<div class="app__content">
|
|
||||||
<div class="welcome-info">
|
|
||||||
<h1>Welcome to {{ .ClientCfg.SiteName }}!</h1>
|
|
||||||
<p>
|
|
||||||
You do not appear to be part of any teams. Please contact your
|
|
||||||
administrator to have him send you an invitation to a private team.
|
|
||||||
Or you can start a new private team.
|
|
||||||
</p>
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
If you where invited to a team that you do not see you must
|
|
||||||
confirm your email address first before gaining access to the
|
|
||||||
team.
|
|
||||||
</div>
|
|
||||||
<div id="new_channel">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
window.setup_welcome_page();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{{end}}
|
|
||||||
Ссылка в новой задаче
Block a user