github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/web/saml.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package web
     5  
     6  import (
     7  	b64 "encoding/base64"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/vnforks/kid/v5/audit"
    12  	"github.com/vnforks/kid/v5/mlog"
    13  	"github.com/vnforks/kid/v5/model"
    14  )
    15  
    16  func (w *Web) InitSaml() {
    17  	w.MainRouter.Handle("/login/sso/saml", w.ApiHandler(loginWithSaml)).Methods("GET")
    18  	w.MainRouter.Handle("/login/sso/saml", w.ApiHandlerTrustRequester(completeSaml)).Methods("POST")
    19  }
    20  
    21  func loginWithSaml(c *Context, w http.ResponseWriter, r *http.Request) {
    22  	samlInterface := c.App.Saml()
    23  
    24  	if samlInterface == nil {
    25  		c.Err = model.NewAppError("loginWithSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound)
    26  		return
    27  	}
    28  
    29  	branchId, err := c.App.GetBranchIdFromQuery(r.URL.Query())
    30  	if err != nil {
    31  		c.Err = err
    32  		return
    33  	}
    34  	action := r.URL.Query().Get("action")
    35  	redirectTo := r.URL.Query().Get("redirect_to")
    36  	relayProps := map[string]string{}
    37  	relayState := ""
    38  
    39  	if len(action) != 0 {
    40  		relayProps["branch_id"] = branchId
    41  		relayProps["action"] = action
    42  		if action == model.OAUTH_ACTION_EMAIL_TO_SSO {
    43  			relayProps["email"] = r.URL.Query().Get("email")
    44  		}
    45  	}
    46  
    47  	if len(redirectTo) != 0 {
    48  		relayProps["redirect_to"] = redirectTo
    49  	}
    50  
    51  	if len(relayProps) > 0 {
    52  		relayState = b64.StdEncoding.EncodeToString([]byte(model.MapToJson(relayProps)))
    53  	}
    54  
    55  	if data, err := samlInterface.BuildRequest(relayState); err != nil {
    56  		c.Err = err
    57  		return
    58  	} else {
    59  		w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
    60  		http.Redirect(w, r, data.URL, http.StatusFound)
    61  	}
    62  }
    63  
    64  func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
    65  	samlInterface := c.App.Saml()
    66  
    67  	if samlInterface == nil {
    68  		c.Err = model.NewAppError("completeSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound)
    69  		return
    70  	}
    71  
    72  	//Validate that the user is with SAML and all that
    73  	encodedXML := r.FormValue("SAMLResponse")
    74  	relayState := r.FormValue("RelayState")
    75  
    76  	relayProps := make(map[string]string)
    77  	if len(relayState) > 0 {
    78  		stateStr := ""
    79  		if b, err := b64.StdEncoding.DecodeString(relayState); err != nil {
    80  			c.Err = model.NewAppError("completeSaml", "api.user.authorize_oauth_user.invalid_state.app_error", nil, err.Error(), http.StatusFound)
    81  			return
    82  		} else {
    83  			stateStr = string(b)
    84  		}
    85  		relayProps = model.MapFromJson(strings.NewReader(stateStr))
    86  	}
    87  
    88  	auditRec := c.MakeAuditRecord("completeSaml", audit.Fail)
    89  	defer c.LogAuditRec(auditRec)
    90  	c.LogAudit("attempt")
    91  
    92  	action := relayProps["action"]
    93  	auditRec.AddMeta("action", action)
    94  
    95  	user, err := samlInterface.DoLogin(encodedXML, relayProps)
    96  	if err != nil {
    97  		c.LogAudit("fail")
    98  		if action == model.OAUTH_ACTION_MOBILE {
    99  			err.Translate(c.App.T)
   100  			w.Write([]byte(err.ToJson()))
   101  		} else {
   102  			c.Err = err
   103  			c.Err.StatusCode = http.StatusFound
   104  		}
   105  		return
   106  	}
   107  
   108  	if err = c.App.CheckUserAllAuthenticationCriteria(user, ""); err != nil {
   109  		c.Err = err
   110  		c.Err.StatusCode = http.StatusFound
   111  		return
   112  	}
   113  
   114  	switch action {
   115  	case model.OAUTH_ACTION_SIGNUP:
   116  		branchId := relayProps["branch_id"]
   117  		if len(branchId) > 0 {
   118  			c.App.Srv().Go(func() {
   119  				if err = c.App.AddUserToBranchByBranchId(branchId, user); err != nil {
   120  					mlog.Error(err.Error())
   121  				} else {
   122  					c.App.AddDirectClasses(branchId, user)
   123  				}
   124  			})
   125  		}
   126  	case model.OAUTH_ACTION_EMAIL_TO_SSO:
   127  		if err = c.App.RevokeAllSessions(user.Id); err != nil {
   128  			c.Err = err
   129  			return
   130  		}
   131  		auditRec.AddMeta("revoked_user_id", user.Id)
   132  		auditRec.AddMeta("revoked", "Revoked all sessions for user")
   133  
   134  		c.LogAuditWithUserId(user.Id, "Revoked all sessions for user")
   135  		c.App.Srv().Go(func() {
   136  			if err = c.App.SendSignInChangeEmail(user.Email, strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO", user.Locale, c.App.GetSiteURL()); err != nil {
   137  				mlog.Error(err.Error())
   138  			}
   139  		})
   140  	}
   141  
   142  	auditRec.AddMeta("obtained_user_id", user.Id)
   143  	c.LogAuditWithUserId(user.Id, "obtained user")
   144  
   145  	err = c.App.DoLogin(w, r, user, "")
   146  	if err != nil {
   147  		c.Err = err
   148  		return
   149  	}
   150  
   151  	auditRec.Success()
   152  	c.LogAuditWithUserId(user.Id, "success")
   153  
   154  	c.App.AttachSessionCookies(w, r)
   155  
   156  	if val, ok := relayProps["redirect_to"]; ok {
   157  		http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusFound)
   158  		return
   159  	}
   160  
   161  	switch action {
   162  	case model.OAUTH_ACTION_MOBILE:
   163  		ReturnStatusOK(w)
   164  	case model.OAUTH_ACTION_EMAIL_TO_SSO:
   165  		http.Redirect(w, r, c.GetSiteURLHeader()+"/login?extra=signin_change", http.StatusFound)
   166  	default:
   167  		http.Redirect(w, r, c.GetSiteURLHeader(), http.StatusFound)
   168  	}
   169  }