Merge pull request #1993 from mattermost/PLT-1586

PLT-1586 adding LDAP/OAuth to iOS
Этот коммит содержится в:
Christopher Speller
2016-01-27 09:22:27 -05:00
родитель e5ed90821e 6fd0f651b9
Коммит f0e6e7de93
16 изменённых файлов: 199 добавлений и 56 удалений

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

@@ -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}}