summaryrefslogtreecommitdiffstats
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/channel_test.go20
-rw-r--r--api/context.go66
-rw-r--r--api/oauth.go2
-rw-r--r--api/team_test.go4
-rw-r--r--api/user.go59
-rw-r--r--api/user_test.go2
6 files changed, 135 insertions, 18 deletions
diff --git a/api/channel_test.go b/api/channel_test.go
index 2414b51e2..11914414b 100644
--- a/api/channel_test.go
+++ b/api/channel_test.go
@@ -95,18 +95,22 @@ func TestCreateChannel(t *testing.T) {
}
isLicensed := utils.IsLicensed
+ license := utils.License
restrictPublicChannel := *utils.Cfg.TeamSettings.RestrictPublicChannelManagement
restrictPrivateChannel := *utils.Cfg.TeamSettings.RestrictPrivateChannelManagement
defer func() {
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel
utils.IsLicensed = isLicensed
+ utils.License = license
utils.SetDefaultRolesBasedOnConfig()
}()
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL
utils.SetDefaultRolesBasedOnConfig()
utils.IsLicensed = true
+ utils.License = &model.License{Features: &model.Features{}}
+ utils.License.Features.SetDefaults()
channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel3 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
@@ -288,17 +292,21 @@ func TestUpdateChannel(t *testing.T) {
}
isLicensed := utils.IsLicensed
+ license := utils.License
restrictPublicChannel := *utils.Cfg.TeamSettings.RestrictPublicChannelManagement
restrictPrivateChannel := *utils.Cfg.TeamSettings.RestrictPrivateChannelManagement
defer func() {
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel
utils.IsLicensed = isLicensed
+ utils.License = license
utils.SetDefaultRolesBasedOnConfig()
}()
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL
utils.IsLicensed = true
+ utils.License = &model.License{Features: &model.Features{}}
+ utils.License.Features.SetDefaults()
utils.SetDefaultRolesBasedOnConfig()
channel2 := th.CreateChannel(Client, team)
@@ -436,17 +444,21 @@ func TestUpdateChannelHeader(t *testing.T) {
}
isLicensed := utils.IsLicensed
+ license := utils.License
restrictPublicChannel := *utils.Cfg.TeamSettings.RestrictPublicChannelManagement
restrictPrivateChannel := *utils.Cfg.TeamSettings.RestrictPrivateChannelManagement
defer func() {
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel
utils.IsLicensed = isLicensed
+ utils.License = license
utils.SetDefaultRolesBasedOnConfig()
}()
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL
utils.IsLicensed = true
+ utils.License = &model.License{Features: &model.Features{}}
+ utils.License.Features.SetDefaults()
utils.SetDefaultRolesBasedOnConfig()
th.LoginBasic()
@@ -566,17 +578,21 @@ func TestUpdateChannelPurpose(t *testing.T) {
}
isLicensed := utils.IsLicensed
+ license := utils.License
restrictPublicChannel := *utils.Cfg.TeamSettings.RestrictPublicChannelManagement
restrictPrivateChannel := *utils.Cfg.TeamSettings.RestrictPrivateChannelManagement
defer func() {
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel
utils.IsLicensed = isLicensed
+ utils.License = license
utils.SetDefaultRolesBasedOnConfig()
}()
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL
utils.IsLicensed = true
+ utils.License = &model.License{Features: &model.Features{}}
+ utils.License.Features.SetDefaults()
utils.SetDefaultRolesBasedOnConfig()
th.LoginBasic()
@@ -1071,17 +1087,21 @@ func TestDeleteChannel(t *testing.T) {
}
isLicensed := utils.IsLicensed
+ license := utils.License
restrictPublicChannel := *utils.Cfg.TeamSettings.RestrictPublicChannelManagement
restrictPrivateChannel := *utils.Cfg.TeamSettings.RestrictPrivateChannelManagement
defer func() {
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel
utils.IsLicensed = isLicensed
+ utils.License = license
utils.SetDefaultRolesBasedOnConfig()
}()
*utils.Cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL
*utils.Cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL
utils.IsLicensed = true
+ utils.License = &model.License{Features: &model.Features{}}
+ utils.License.Features.SetDefaults()
utils.SetDefaultRolesBasedOnConfig()
th.LoginSystemAdmin()
diff --git a/api/context.go b/api/context.go
index 4c2e9d489..1e82acb68 100644
--- a/api/context.go
+++ b/api/context.go
@@ -47,51 +47,55 @@ type Context struct {
}
func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, false, false, true, false, false, false}
+ return &handler{h, false, false, true, false, false, false, false}
}
func AppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, false, false, false, false, false, false}
+ return &handler{h, false, false, false, false, false, false, false}
}
func AppHandlerIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, false, false, false, false, true, false}
+ return &handler{h, false, false, false, false, true, false, false}
}
func ApiUserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, true, false, true, false, false, false}
+ return &handler{h, true, false, true, false, false, false, true}
}
func ApiUserRequiredActivity(h func(*Context, http.ResponseWriter, *http.Request), isUserActivity bool) http.Handler {
- return &handler{h, true, false, true, isUserActivity, false, false}
+ return &handler{h, true, false, true, isUserActivity, false, false, true}
+}
+
+func ApiUserRequiredMfa(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
+ return &handler{h, true, false, true, false, false, false, false}
}
func UserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, true, false, false, false, false, false}
+ return &handler{h, true, false, false, false, false, false, true}
}
func AppHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, false, false, false, false, false, true}
+ return &handler{h, false, false, false, false, false, true, false}
}
func ApiAdminSystemRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, true, true, true, false, false, false}
+ return &handler{h, true, true, true, false, false, false, true}
}
func ApiAdminSystemRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, true, true, true, false, false, true}
+ return &handler{h, true, true, true, false, false, true, true}
}
func ApiAppHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, false, false, true, false, false, true}
+ return &handler{h, false, false, true, false, false, true, false}
}
func ApiUserRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, true, false, true, false, false, true}
+ return &handler{h, true, false, true, false, false, true, true}
}
func ApiAppHandlerTrustRequesterIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
- return &handler{h, false, false, true, false, true, true}
+ return &handler{h, false, false, true, false, true, true, false}
}
type handler struct {
@@ -102,6 +106,7 @@ type handler struct {
isUserActivity bool
isTeamIndependent bool
trustRequester bool
+ requireMfa bool
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -204,6 +209,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.UserRequired()
}
+ if c.Err == nil && h.requireMfa {
+ c.MfaRequired()
+ }
+
if c.Err == nil && h.requireSystemAdmin {
c.SystemAdminRequired()
}
@@ -331,6 +340,39 @@ func (c *Context) UserRequired() {
}
}
+func (c *Context) MfaRequired() {
+ // Must be licensed for MFA and have it configured for enforcement
+ if !utils.IsLicensed || !*utils.License.Features.MFA || !*utils.Cfg.ServiceSettings.EnableMultifactorAuthentication || !*utils.Cfg.ServiceSettings.EnforceMultifactorAuthentication {
+ return
+ }
+
+ // OAuth integrations are excepted
+ if c.Session.IsOAuth {
+ return
+ }
+
+ if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
+ c.Err = model.NewLocAppError("", "api.context.session_expired.app_error", nil, "MfaRequired")
+ c.Err.StatusCode = http.StatusUnauthorized
+ return
+ } else {
+ user := result.Data.(*model.User)
+
+ // Only required for email and ldap accounts
+ if user.AuthService != "" &&
+ user.AuthService != model.USER_AUTH_SERVICE_EMAIL &&
+ user.AuthService != model.USER_AUTH_SERVICE_LDAP {
+ return
+ }
+
+ if !user.MfaActive {
+ c.Err = model.NewLocAppError("", "api.context.mfa_required.app_error", nil, "MfaRequired")
+ c.Err.StatusCode = http.StatusUnauthorized
+ return
+ }
+ }
+}
+
func (c *Context) SystemAdminRequired() {
if len(c.Session.UserId) == 0 {
c.Err = model.NewLocAppError("", "api.context.session_expired.app_error", nil, "SystemAdminRequired")
diff --git a/api/oauth.go b/api/oauth.go
index 08c763b61..268cf1aed 100644
--- a/api/oauth.go
+++ b/api/oauth.go
@@ -856,7 +856,7 @@ func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request,
return
}
- if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, &authData, ssoEmail); result.Err != nil {
+ if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, &authData, ssoEmail, true); result.Err != nil {
c.Err = result.Err
return
}
diff --git a/api/team_test.go b/api/team_test.go
index 5880ffcff..2af46f4dc 100644
--- a/api/team_test.go
+++ b/api/team_test.go
@@ -426,10 +426,14 @@ func TestInviteMembers(t *testing.T) {
}
isLicensed := utils.IsLicensed
+ license := utils.License
defer func() {
utils.IsLicensed = isLicensed
+ utils.License = license
}()
utils.IsLicensed = true
+ utils.License = &model.License{Features: &model.Features{}}
+ utils.License.Features.SetDefaults()
if _, err := Client.InviteMembers(invites); err == nil {
t.Fatal("should have errored not team admin and licensed")
diff --git a/api/user.go b/api/user.go
index 2085ccb60..0e74a577c 100644
--- a/api/user.go
+++ b/api/user.go
@@ -65,8 +65,8 @@ func InitUser() {
BaseRoutes.NeedChannel.Handle("/users/autocomplete", ApiUserRequired(autocompleteUsersInChannel)).Methods("GET")
BaseRoutes.Users.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST")
- BaseRoutes.Users.Handle("/generate_mfa_secret", ApiUserRequiredTrustRequester(generateMfaSecret)).Methods("GET")
- BaseRoutes.Users.Handle("/update_mfa", ApiUserRequired(updateMfa)).Methods("POST")
+ BaseRoutes.Users.Handle("/generate_mfa_secret", ApiUserRequiredMfa(generateMfaSecret)).Methods("GET")
+ BaseRoutes.Users.Handle("/update_mfa", ApiUserRequiredMfa(updateMfa)).Methods("POST")
BaseRoutes.Users.Handle("/claim/email_to_oauth", ApiAppHandler(emailToOAuth)).Methods("POST")
BaseRoutes.Users.Handle("/claim/oauth_to_email", ApiUserRequired(oauthToEmail)).Methods("POST")
@@ -1875,6 +1875,30 @@ func sendPasswordChangeEmail(c *Context, email, siteURL, method string) {
}
}
+func sendMfaChangeEmail(c *Context, email string, siteURL string, activated bool) {
+ subject := c.T("api.templates.mfa_change_subject",
+ map[string]interface{}{"SiteName": utils.Cfg.TeamSettings.SiteName})
+
+ bodyPage := utils.NewHTMLTemplate("mfa_change_body", c.Locale)
+ bodyPage.Props["SiteURL"] = siteURL
+
+ bodyText := ""
+ if activated {
+ bodyText = "api.templates.mfa_activated_body.info"
+ bodyPage.Props["Title"] = c.T("api.templates.mfa_activated_body.title")
+ } else {
+ bodyText = "api.templates.mfa_deactivated_body.info"
+ bodyPage.Props["Title"] = c.T("api.templates.mfa_deactivated_body.title")
+ }
+
+ bodyPage.Html["Info"] = template.HTML(c.T(bodyText,
+ map[string]interface{}{"SiteURL": siteURL}))
+
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
+ l4g.Error(utils.T("api.user.send_mfa_change_email.error"), err)
+ }
+}
+
func sendEmailChangeEmail(c *Context, oldEmail, newEmail, siteURL string) {
subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, c.T("api.templates.email_change_subject",
map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}))
@@ -2016,6 +2040,8 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ mfaToken := props["token"]
+
service := props["service"]
if len(service) == 0 {
c.SetInvalidParam("emailToOAuth", "service")
@@ -2039,7 +2065,7 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
user = result.Data.(*model.User)
}
- if err := checkPasswordAndAllCriteria(user, password, ""); err != nil {
+ if err := checkPasswordAndAllCriteria(user, password, mfaToken); err != nil {
c.LogAuditWithUserId(user.Id, "failed - bad authentication")
c.Err = err
return
@@ -2147,6 +2173,8 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ token := props["token"]
+
c.LogAudit("attempt")
var user *model.User
@@ -2158,7 +2186,7 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) {
user = result.Data.(*model.User)
}
- if err := checkPasswordAndAllCriteria(user, emailPassword, ""); err != nil {
+ if err := checkPasswordAndAllCriteria(user, emailPassword, token); err != nil {
c.LogAuditWithUserId(user.Id, "failed - bad authentication")
c.Err = err
return
@@ -2213,6 +2241,8 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ token := props["token"]
+
c.LogAudit("attempt")
var user *model.User
@@ -2242,6 +2272,12 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ if err := checkUserMfa(user, token); err != nil {
+ c.LogAuditWithUserId(user.Id, "fail - mfa token failed")
+ c.Err = err
+ return
+ }
+
if result := <-Srv.Store.User().UpdatePassword(user.Id, model.HashPassword(emailPassword)); result.Err != nil {
c.LogAudit("fail - database issue")
c.Err = result.Err
@@ -2379,18 +2415,33 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+ c.LogAudit("attempt")
+
if activate {
if err := ActivateMfa(c.Session.UserId, token); err != nil {
c.Err = err
return
}
+ c.LogAudit("success - activated")
} else {
if err := DeactivateMfa(c.Session.UserId); err != nil {
c.Err = err
return
}
+ c.LogAudit("success - deactivated")
}
+ go func() {
+ var user *model.User
+ if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
+ l4g.Warn(result.Err)
+ } else {
+ user = result.Data.(*model.User)
+ }
+
+ sendMfaChangeEmail(c, user.Email, c.GetSiteURL(), activate)
+ }()
+
rdata := map[string]string{}
rdata["status"] = "ok"
w.Write([]byte(model.MapToJson(rdata)))
diff --git a/api/user_test.go b/api/user_test.go
index 85af7e598..65bdcb653 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -1341,7 +1341,7 @@ func TestResetPassword(t *testing.T) {
}
authData := model.NewId()
- if result := <-Srv.Store.User().UpdateAuthData(user.Id, "random", &authData, ""); result.Err != nil {
+ if result := <-Srv.Store.User().UpdateAuthData(user.Id, "random", &authData, "", true); result.Err != nil {
t.Fatal(result.Err)
}