GH-11192 Move non-API OAuth endpoints from api4 to web package (#11327)
* GH-11192 WIP * GH-11192 WIP * GH-11192 tidy up * GH-11192 rename handlers * GH-11192 add TestAuthorizeOAuthApp * GH-11192 WIP * GH-11192 Tests mostly passing * GH-11192 add missing closeBody function back * GH-11192 add test api endpoint * GH-11192 rename endpoint to oauth_test
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
faa1898410
Коммит
50011d5589
370
api4/oauth.go
370
api4/oauth.go
@@ -5,15 +5,8 @@ package api4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/app"
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
"github.com/mattermost/mattermost-server/utils/fileutils"
|
||||
)
|
||||
|
||||
func (api *API) InitOAuth() {
|
||||
@@ -26,23 +19,6 @@ func (api *API) InitOAuth() {
|
||||
api.BaseRoutes.OAuthApp.Handle("/regen_secret", api.ApiSessionRequired(regenerateOAuthAppSecret)).Methods("POST")
|
||||
|
||||
api.BaseRoutes.User.Handle("/oauth/apps/authorized", api.ApiSessionRequired(getAuthorizedOAuthApps)).Methods("GET")
|
||||
|
||||
// API version independent OAuth 2.0 as a service provider endpoints
|
||||
api.BaseRoutes.Root.Handle("/oauth/authorize", api.ApiHandlerTrustRequester(authorizeOAuthPage)).Methods("GET")
|
||||
api.BaseRoutes.Root.Handle("/oauth/authorize", api.ApiSessionRequired(authorizeOAuthApp)).Methods("POST")
|
||||
api.BaseRoutes.Root.Handle("/oauth/deauthorize", api.ApiSessionRequired(deauthorizeOAuthApp)).Methods("POST")
|
||||
api.BaseRoutes.Root.Handle("/oauth/access_token", api.ApiHandlerTrustRequester(getAccessToken)).Methods("POST")
|
||||
|
||||
// API version independent OAuth as a client endpoints
|
||||
api.BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/complete", api.ApiHandler(completeOAuth)).Methods("GET")
|
||||
api.BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/login", api.ApiHandler(loginWithOAuth)).Methods("GET")
|
||||
api.BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/mobile_login", api.ApiHandler(mobileLoginWithOAuth)).Methods("GET")
|
||||
api.BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/signup", api.ApiHandler(signupWithOAuth)).Methods("GET")
|
||||
|
||||
// Old endpoints for backwards compatibility, needed to not break SSO for any old setups
|
||||
api.BaseRoutes.Root.Handle("/api/v3/oauth/{service:[A-Za-z0-9]+}/complete", api.ApiHandler(completeOAuth)).Methods("GET")
|
||||
api.BaseRoutes.Root.Handle("/signup/{service:[A-Za-z0-9]+}/complete", api.ApiHandler(completeOAuth)).Methods("GET")
|
||||
api.BaseRoutes.Root.Handle("/login/{service:[A-Za-z0-9]+}/complete", api.ApiHandler(completeOAuth)).Methods("GET")
|
||||
}
|
||||
|
||||
func createOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -273,349 +249,3 @@ func getAuthorizedOAuthApps(c *Context, w http.ResponseWriter, r *http.Request)
|
||||
|
||||
w.Write([]byte(model.OAuthAppListToJson(apps)))
|
||||
}
|
||||
|
||||
func authorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
authRequest := model.AuthorizeRequestFromJson(r.Body)
|
||||
if authRequest == nil {
|
||||
c.SetInvalidParam("authorize_request")
|
||||
}
|
||||
|
||||
if err := authRequest.IsValid(); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if c.App.Session.IsOAuth {
|
||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||
c.Err.DetailedError += ", attempted access by oauth app"
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("attempt")
|
||||
|
||||
redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.App.Session.UserId, authRequest)
|
||||
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("")
|
||||
|
||||
w.Write([]byte(model.MapToJson(map[string]string{"redirect": redirectUrl})))
|
||||
}
|
||||
|
||||
func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
requestData := model.MapFromJson(r.Body)
|
||||
clientId := requestData["client_id"]
|
||||
|
||||
if len(clientId) != 26 {
|
||||
c.SetInvalidParam("client_id")
|
||||
return
|
||||
}
|
||||
|
||||
err := c.App.DeauthorizeOAuthAppForUser(c.App.Session.UserId, clientId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("success")
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !*c.App.Config().ServiceSettings.EnableOAuthServiceProvider {
|
||||
err := model.NewAppError("authorizeOAuth", "api.oauth.authorize_oauth.disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: r.URL.Query().Get("response_type"),
|
||||
ClientId: r.URL.Query().Get("client_id"),
|
||||
RedirectUri: r.URL.Query().Get("redirect_uri"),
|
||||
Scope: r.URL.Query().Get("scope"),
|
||||
State: r.URL.Query().Get("state"),
|
||||
}
|
||||
|
||||
loginHint := r.URL.Query().Get("login_hint")
|
||||
|
||||
if err := authRequest.IsValid(); err != nil {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
oauthApp, err := c.App.GetOAuthApp(authRequest.ClientId)
|
||||
if err != nil {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
// here we should check if the user is logged in
|
||||
if len(c.App.Session.UserId) == 0 {
|
||||
if loginHint == model.USER_AUTH_SERVICE_SAML {
|
||||
http.Redirect(w, r, c.GetSiteURLHeader()+"/login/sso/saml?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound)
|
||||
} else {
|
||||
http.Redirect(w, r, c.GetSiteURLHeader()+"/login?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !oauthApp.IsValidRedirectURL(authRequest.RedirectUri) {
|
||||
err := model.NewAppError("authorizeOAuthPage", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest)
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
isAuthorized := false
|
||||
|
||||
if _, err := c.App.GetPreferenceByCategoryAndNameForUser(c.App.Session.UserId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, authRequest.ClientId); err == nil {
|
||||
// when we support scopes we should check if the scopes match
|
||||
isAuthorized = true
|
||||
}
|
||||
|
||||
// Automatically allow if the app is trusted
|
||||
if oauthApp.IsTrusted || isAuthorized {
|
||||
redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.App.Session.UserId, authRequest)
|
||||
|
||||
if err != nil {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, redirectUrl, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
w.Header().Set("Content-Security-Policy", "frame-ancestors 'self'")
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-cache, max-age=31556926, public")
|
||||
|
||||
staticDir, _ := fileutils.FindDir(model.CLIENT_DIR)
|
||||
http.ServeFile(w, r, filepath.Join(staticDir, "root.html"))
|
||||
}
|
||||
|
||||
func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
|
||||
code := r.FormValue("code")
|
||||
refreshToken := r.FormValue("refresh_token")
|
||||
|
||||
grantType := r.FormValue("grant_type")
|
||||
switch grantType {
|
||||
case model.ACCESS_TOKEN_GRANT_TYPE:
|
||||
if len(code) == 0 {
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.missing_code.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
case model.REFRESH_TOKEN_GRANT_TYPE:
|
||||
if len(refreshToken) == 0 {
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.missing_refresh_token.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
default:
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_grant.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
clientId := r.FormValue("client_id")
|
||||
if len(clientId) != 26 {
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_client_id.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
secret := r.FormValue("client_secret")
|
||||
if len(secret) == 0 {
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_client_secret.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
redirectUri := r.FormValue("redirect_uri")
|
||||
|
||||
c.LogAudit("attempt")
|
||||
|
||||
accessRsp, err := c.App.GetOAuthAccessTokenForCodeFlow(clientId, grantType, redirectUri, code, secret, refreshToken)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
|
||||
c.LogAudit("success")
|
||||
|
||||
w.Write([]byte(accessRsp.ToJson()))
|
||||
}
|
||||
|
||||
func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireService()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
service := c.Params.Service
|
||||
|
||||
oauthError := r.URL.Query().Get("error")
|
||||
if oauthError == "access_denied" {
|
||||
utils.RenderWebError(c.App.Config(), w, r, http.StatusTemporaryRedirect, url.Values{
|
||||
"type": []string{"oauth_access_denied"},
|
||||
"service": []string{strings.Title(service)},
|
||||
}, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
code := r.URL.Query().Get("code")
|
||||
if len(code) == 0 {
|
||||
utils.RenderWebError(c.App.Config(), w, r, http.StatusTemporaryRedirect, url.Values{
|
||||
"type": []string{"oauth_missing_code"},
|
||||
"service": []string{strings.Title(service)},
|
||||
}, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
state := r.URL.Query().Get("state")
|
||||
|
||||
uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete"
|
||||
|
||||
body, teamId, props, err := c.App.AuthorizeOAuthUser(w, r, service, code, state, uri)
|
||||
|
||||
action := ""
|
||||
if props != nil {
|
||||
action = props["action"]
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err.Translate(c.App.T)
|
||||
mlog.Error(err.Error())
|
||||
if action == model.OAUTH_ACTION_MOBILE {
|
||||
w.Write([]byte(err.ToJson()))
|
||||
} else {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
user, err := c.App.CompleteOAuth(service, body, teamId, props)
|
||||
if err != nil {
|
||||
err.Translate(c.App.T)
|
||||
mlog.Error(err.Error())
|
||||
if action == model.OAUTH_ACTION_MOBILE {
|
||||
w.Write([]byte(err.ToJson()))
|
||||
} else {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var redirectUrl string
|
||||
if action == model.OAUTH_ACTION_EMAIL_TO_SSO {
|
||||
redirectUrl = c.GetSiteURLHeader() + "/login?extra=signin_change"
|
||||
} else if action == model.OAUTH_ACTION_SSO_TO_EMAIL {
|
||||
redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"])
|
||||
} else {
|
||||
session, err := c.App.DoLogin(w, r, user, "")
|
||||
if err != nil {
|
||||
err.Translate(c.App.T)
|
||||
c.Err = err
|
||||
if action == model.OAUTH_ACTION_MOBILE {
|
||||
w.Write([]byte(err.ToJson()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.App.AttachSessionCookies(w, r, session)
|
||||
|
||||
c.App.Session = *session
|
||||
|
||||
if _, ok := props["redirect_to"]; ok {
|
||||
redirectUrl = props["redirect_to"]
|
||||
} else {
|
||||
redirectUrl = c.GetSiteURLHeader()
|
||||
}
|
||||
}
|
||||
|
||||
if action == model.OAUTH_ACTION_MOBILE {
|
||||
ReturnStatusOK(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireService()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
loginHint := r.URL.Query().Get("login_hint")
|
||||
redirectTo := r.URL.Query().Get("redirect_to")
|
||||
|
||||
teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
authUrl, err := c.App.GetOAuthLoginEndpoint(w, r, c.Params.Service, teamId, model.OAUTH_ACTION_LOGIN, redirectTo, loginHint)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, authUrl, http.StatusFound)
|
||||
}
|
||||
|
||||
func mobileLoginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireService()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
authUrl, err := c.App.GetOAuthLoginEndpoint(w, r, c.Params.Service, teamId, model.OAUTH_ACTION_MOBILE, "", "")
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, authUrl, http.StatusFound)
|
||||
}
|
||||
|
||||
func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireService()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !*c.App.Config().TeamSettings.EnableUserCreation {
|
||||
utils.RenderWebError(c.App.Config(), w, r, http.StatusBadRequest, url.Values{
|
||||
"message": []string{utils.T("api.oauth.singup_with_oauth.disabled.app_error")},
|
||||
}, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
authUrl, err := c.App.GetOAuthSignupEndpoint(w, r, c.Params.Service, teamId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, authUrl, http.StatusFound)
|
||||
}
|
||||
|
||||
@@ -4,22 +4,12 @@
|
||||
package api4
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
"github.com/mattermost/mattermost-server/web"
|
||||
)
|
||||
|
||||
func TestCreateOAuthApp(t *testing.T) {
|
||||
@@ -642,578 +632,9 @@ func TestGetAuthorizedOAuthAppsForUser(t *testing.T) {
|
||||
CheckNoError(t, resp)
|
||||
}
|
||||
|
||||
func TestAuthorizeOAuthApp(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
AdminClient := th.SystemAdminClient
|
||||
|
||||
enableOAuth := *th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
|
||||
|
||||
rapp, resp := AdminClient.CreateOAuthApp(oapp)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: rapp.Id,
|
||||
RedirectUri: rapp.CallbackUrls[0],
|
||||
Scope: "",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
// Test auth code flow
|
||||
ruri, resp := Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(ruri) == 0 {
|
||||
t.Fatal("redirect url should be set")
|
||||
}
|
||||
|
||||
ru, _ := url.Parse(ruri)
|
||||
if ru == nil {
|
||||
t.Fatal("redirect url unparseable")
|
||||
} else {
|
||||
if len(ru.Query().Get("code")) == 0 {
|
||||
t.Fatal("authorization code not returned")
|
||||
}
|
||||
if ru.Query().Get("state") != authRequest.State {
|
||||
t.Fatal("returned state doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
// Test implicit flow
|
||||
authRequest.ResponseType = model.IMPLICIT_RESPONSE_TYPE
|
||||
ruri, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
require.False(t, len(ruri) == 0, "redirect url should be set")
|
||||
|
||||
ru, _ = url.Parse(ruri)
|
||||
require.NotNil(t, ru, "redirect url unparseable")
|
||||
values, err := url.ParseQuery(ru.Fragment)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, len(values.Get("access_token")) == 0, "access_token not returned")
|
||||
assert.Equal(t, authRequest.State, values.Get("state"), "returned state doesn't match")
|
||||
|
||||
oldToken := Client.AuthToken
|
||||
Client.AuthToken = values.Get("access_token")
|
||||
_, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
Client.AuthToken = oldToken
|
||||
|
||||
authRequest.RedirectUri = ""
|
||||
_, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.RedirectUri = "http://somewhereelse.com"
|
||||
_, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.RedirectUri = rapp.CallbackUrls[0]
|
||||
authRequest.ResponseType = ""
|
||||
_, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.ResponseType = model.AUTHCODE_RESPONSE_TYPE
|
||||
authRequest.ClientId = ""
|
||||
_, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.ClientId = model.NewId()
|
||||
_, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestDeauthorizeOAuthApp(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
AdminClient := th.SystemAdminClient
|
||||
|
||||
enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
|
||||
|
||||
rapp, resp := AdminClient.CreateOAuthApp(oapp)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: rapp.Id,
|
||||
RedirectUri: rapp.CallbackUrls[0],
|
||||
Scope: "",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
_, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
pass, resp := Client.DeauthorizeOAuthApp(rapp.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if !pass {
|
||||
t.Fatal("should have passed")
|
||||
}
|
||||
|
||||
_, resp = Client.DeauthorizeOAuthApp("junk")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
_, resp = Client.DeauthorizeOAuthApp(model.NewId())
|
||||
CheckNoError(t, resp)
|
||||
|
||||
Client.Logout()
|
||||
_, resp = Client.DeauthorizeOAuthApp(rapp.Id)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestOAuthAccessToken(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
|
||||
enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID)
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||
|
||||
oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
|
||||
oauthApp = Client.Must(Client.CreateOAuthApp(oauthApp)).(*model.OAuthApp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
|
||||
data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{"12345678901234567890123456"}, "client_secret": []string{"12345678901234567890123456"}, "code": []string{"junk"}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}}
|
||||
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Log(resp.StatusCode)
|
||||
t.Fatal("should have failed - oauth providing turned off")
|
||||
}
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: oauthApp.Id,
|
||||
RedirectUri: oauthApp.CallbackUrls[0],
|
||||
Scope: "all",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
redirect, resp := Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ := url.Parse(redirect)
|
||||
|
||||
Client.Logout()
|
||||
|
||||
data = url.Values{"grant_type": []string{"junk"}, "client_id": []string{oauthApp.Id}, "client_secret": []string{oauthApp.ClientSecret}, "code": []string{rurl.Query().Get("code")}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}}
|
||||
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad grant type")
|
||||
}
|
||||
|
||||
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", "")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - missing client id")
|
||||
}
|
||||
data.Set("client_id", "junk")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad client id")
|
||||
}
|
||||
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", "")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - missing client secret")
|
||||
}
|
||||
|
||||
data.Set("client_secret", "junk")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad client secret")
|
||||
}
|
||||
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("code", "")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - missing code")
|
||||
}
|
||||
|
||||
data.Set("code", "junk")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad code")
|
||||
}
|
||||
|
||||
data.Set("code", rurl.Query().Get("code"))
|
||||
data.Set("redirect_uri", "junk")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - non-matching redirect uri")
|
||||
}
|
||||
|
||||
// reset data for successful request
|
||||
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("code", rurl.Query().Get("code"))
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
|
||||
token := ""
|
||||
refreshToken := ""
|
||||
if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(rsp.AccessToken) == 0 {
|
||||
t.Fatal("access token not returned")
|
||||
} else if len(rsp.RefreshToken) == 0 {
|
||||
t.Fatal("refresh token not returned")
|
||||
} else {
|
||||
token = rsp.AccessToken
|
||||
refreshToken = rsp.RefreshToken
|
||||
}
|
||||
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
|
||||
t.Fatal("access token type incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := Client.DoApiGet("/users?page=0&per_page=100&access_token="+token, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, resp := Client.GetUsers(0, 100, ""); resp.Error == nil {
|
||||
t.Fatal("should have failed - no access token provided")
|
||||
}
|
||||
|
||||
if _, resp := Client.GetUsers(0, 100, ""); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad access token provided")
|
||||
}
|
||||
|
||||
Client.SetOAuthToken(token)
|
||||
if users, resp := Client.GetUsers(0, 100, ""); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(users) == 0 {
|
||||
t.Fatal("users empty - did not get results correctly")
|
||||
}
|
||||
}
|
||||
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - tried to reuse auth code")
|
||||
}
|
||||
|
||||
data.Set("grant_type", model.REFRESH_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("refresh_token", "")
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
data.Del("code")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("Should have failed - refresh token empty")
|
||||
}
|
||||
|
||||
data.Set("refresh_token", refreshToken)
|
||||
if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(rsp.AccessToken) == 0 {
|
||||
t.Fatal("access token not returned")
|
||||
} else if len(rsp.RefreshToken) == 0 {
|
||||
t.Fatal("refresh token not returned")
|
||||
} else if rsp.RefreshToken == refreshToken {
|
||||
t.Fatal("refresh token did not update")
|
||||
}
|
||||
|
||||
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
|
||||
t.Fatal("access token type incorrect")
|
||||
}
|
||||
Client.SetOAuthToken(rsp.AccessToken)
|
||||
_, resp = Client.GetMe("")
|
||||
if resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
}
|
||||
|
||||
data.Set("refresh_token", rsp.RefreshToken)
|
||||
}
|
||||
|
||||
if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(rsp.AccessToken) == 0 {
|
||||
t.Fatal("access token not returned")
|
||||
} else if len(rsp.RefreshToken) == 0 {
|
||||
t.Fatal("refresh token not returned")
|
||||
} else if rsp.RefreshToken == refreshToken {
|
||||
t.Fatal("refresh token did not update")
|
||||
}
|
||||
|
||||
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
|
||||
t.Fatal("access token type incorrect")
|
||||
}
|
||||
Client.SetOAuthToken(rsp.AccessToken)
|
||||
_, resp = Client.GetMe("")
|
||||
if resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
authData := &model.AuthData{ClientId: oauthApp.Id, RedirectUri: oauthApp.CallbackUrls[0], UserId: th.BasicUser.Id, Code: model.NewId(), ExpiresIn: -1}
|
||||
_, err := th.App.Srv.Store.OAuth().SaveAuthData(authData)
|
||||
require.Nil(t, err)
|
||||
|
||||
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
data.Set("code", authData.Code)
|
||||
data.Del("refresh_token")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("Should have failed - code is expired")
|
||||
}
|
||||
|
||||
Client.ClearOAuthToken()
|
||||
}
|
||||
|
||||
func TestOAuthComplete(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
|
||||
gitLabSettingsEnable := th.App.Config().GitLabSettings.Enable
|
||||
gitLabSettingsAuthEndpoint := th.App.Config().GitLabSettings.AuthEndpoint
|
||||
gitLabSettingsId := th.App.Config().GitLabSettings.Id
|
||||
gitLabSettingsSecret := th.App.Config().GitLabSettings.Secret
|
||||
gitLabSettingsTokenEndpoint := th.App.Config().GitLabSettings.TokenEndpoint
|
||||
gitLabSettingsUserApiEndpoint := th.App.Config().GitLabSettings.UserApiEndpoint
|
||||
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Enable = gitLabSettingsEnable })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = gitLabSettingsAuthEndpoint })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = gitLabSettingsId })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Secret = gitLabSettingsSecret })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.TokenEndpoint = gitLabSettingsTokenEndpoint })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.UserApiEndpoint = gitLabSettingsUserApiEndpoint })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
|
||||
}()
|
||||
|
||||
r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123", Client.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Enable = true })
|
||||
r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state=!#$#F@#Yˆ&~ñ", Client.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Id = model.NewId() })
|
||||
|
||||
stateProps := map[string]string{}
|
||||
stateProps["action"] = model.OAUTH_ACTION_LOGIN
|
||||
stateProps["team_id"] = th.BasicTeam.Id
|
||||
stateProps["redirect_to"] = *th.App.Config().GitLabSettings.AuthEndpoint
|
||||
|
||||
state := base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
stateProps["hash"] = utils.HashSha256(*th.App.Config().GitLabSettings.Id)
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
// We are going to use mattermost as the provider emulating gitlab
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID)
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||
|
||||
oauthApp := &model.OAuthApp{
|
||||
Name: "TestApp5" + model.NewId(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{
|
||||
Client.Url + "/signup/" + model.SERVICE_GITLAB + "/complete",
|
||||
Client.Url + "/login/" + model.SERVICE_GITLAB + "/complete",
|
||||
},
|
||||
IsTrusted: true,
|
||||
}
|
||||
oauthApp = Client.Must(Client.CreateOAuthApp(oauthApp)).(*model.OAuthApp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Id = oauthApp.Id })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Secret = oauthApp.ClientSecret })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.TokenEndpoint = Client.Url + "/oauth/access_token" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.UserApiEndpoint = Client.ApiUrl + "/users/me" })
|
||||
|
||||
provider := &MattermostTestProvider{}
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: oauthApp.Id,
|
||||
RedirectUri: oauthApp.CallbackUrls[0],
|
||||
Scope: "all",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
redirect, resp := Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ := url.Parse(redirect)
|
||||
|
||||
code := rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO
|
||||
delete(stateProps, "team_id")
|
||||
stateProps["redirect_to"] = *th.App.Config().GitLabSettings.AuthEndpoint
|
||||
stateProps["hash"] = utils.HashSha256(*th.App.Config().GitLabSettings.Id)
|
||||
stateProps["redirect_to"] = "/oauth/authorize"
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
einterfaces.RegisterOauthProvider(model.SERVICE_GITLAB, provider)
|
||||
|
||||
redirect, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
if _, err := th.App.Srv.Store.User().UpdateAuthData(
|
||||
th.BasicUser.Id, model.SERVICE_GITLAB, &th.BasicUser.Email, th.BasicUser.Email, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
redirect, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAUTH_ACTION_LOGIN
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
redirect, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
delete(stateProps, "action")
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
redirect, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAUTH_ACTION_SIGNUP
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthComplete_AccessDenied(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
Params: &web.Params{
|
||||
Service: "TestService",
|
||||
},
|
||||
}
|
||||
responseWriter := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/signup/TestService/complete?error=access_denied", nil)
|
||||
|
||||
completeOAuth(c, responseWriter, request)
|
||||
|
||||
response := responseWriter.Result()
|
||||
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, response.StatusCode)
|
||||
|
||||
location, _ := url.Parse(response.Header.Get("Location"))
|
||||
assert.Equal(t, "oauth_access_denied", location.Query().Get("type"))
|
||||
assert.Equal(t, "TestService", location.Query().Get("service"))
|
||||
}
|
||||
|
||||
func HttpGet(url string, httpClient *http.Client, authToken string, followRedirect bool) (*http.Response, *model.AppError) {
|
||||
rq, _ := http.NewRequest("GET", url, nil)
|
||||
rq.Close = true
|
||||
|
||||
if len(authToken) > 0 {
|
||||
rq.Header.Set(model.HEADER_AUTH, authToken)
|
||||
}
|
||||
|
||||
if !followRedirect {
|
||||
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
}
|
||||
|
||||
if rp, err := httpClient.Do(rq); err != nil {
|
||||
return nil, model.NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)
|
||||
} else if rp.StatusCode == 304 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode == 307 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode >= 300 {
|
||||
defer closeBody(rp)
|
||||
return rp, model.AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
return rp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func closeBody(r *http.Response) {
|
||||
if r != nil && r.Body != nil {
|
||||
ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type MattermostTestProvider struct {
|
||||
}
|
||||
|
||||
func (m *MattermostTestProvider) GetUserFromJson(data io.Reader) *model.User {
|
||||
user := model.UserFromJson(data)
|
||||
user.AuthData = &user.Email
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/NYTimes/gziphandler"
|
||||
|
||||
"github.com/mattermost/mattermost-server/app"
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
@@ -240,3 +242,73 @@ func (h *Handler) checkCSRFToken(c *Context, r *http.Request, token string, toke
|
||||
|
||||
return csrfCheckNeeded, csrfCheckPassed
|
||||
}
|
||||
|
||||
// ApiHandler provides a handler for API endpoints which do not require the user to be logged in order for access to be
|
||||
// granted.
|
||||
func (w *Web) ApiHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
handler := &Handler{
|
||||
GetGlobalAppOptions: w.GetGlobalAppOptions,
|
||||
HandleFunc: h,
|
||||
RequireSession: false,
|
||||
TrustRequester: false,
|
||||
RequireMfa: false,
|
||||
IsStatic: false,
|
||||
}
|
||||
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
// ApiHandlerTrustRequester provides a handler for API endpoints which do not require the user to be logged in and are
|
||||
// allowed to be requested directly rather than via javascript/XMLHttpRequest, such as site branding images or the
|
||||
// websocket.
|
||||
func (w *Web) ApiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
handler := &Handler{
|
||||
GetGlobalAppOptions: w.GetGlobalAppOptions,
|
||||
HandleFunc: h,
|
||||
RequireSession: false,
|
||||
TrustRequester: true,
|
||||
RequireMfa: false,
|
||||
IsStatic: false,
|
||||
}
|
||||
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
// ApiSessionRequired provides a handler for API endpoints which require the user to be logged in in order for access to
|
||||
// be granted.
|
||||
func (w *Web) ApiSessionRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
handler := &Handler{
|
||||
GetGlobalAppOptions: w.GetGlobalAppOptions,
|
||||
HandleFunc: h,
|
||||
RequireSession: true,
|
||||
TrustRequester: false,
|
||||
RequireMfa: true,
|
||||
IsStatic: false,
|
||||
}
|
||||
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
// apiHandlerTrustRequester provides a handler for API endpoints which do not require the user to be logged in and are
|
||||
// allowed to be requested directly rather than via javascript/XMLHttpRequest, such as site branding images or the
|
||||
// websocket.
|
||||
func (w *Web) apiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
handler := &Handler{
|
||||
GetGlobalAppOptions: w.GetGlobalAppOptions,
|
||||
HandleFunc: h,
|
||||
RequireSession: false,
|
||||
TrustRequester: true,
|
||||
RequireMfa: false,
|
||||
IsStatic: false,
|
||||
}
|
||||
if *w.ConfigService.Config().ServiceSettings.WebserverMode == "gzip" {
|
||||
return gziphandler.GzipHandler(handler)
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
387
web/oauth.go
Обычный файл
387
web/oauth.go
Обычный файл
@@ -0,0 +1,387 @@
|
||||
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/app"
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
"github.com/mattermost/mattermost-server/utils/fileutils"
|
||||
)
|
||||
|
||||
func (w *Web) InitOAuth() {
|
||||
// API version independent OAuth 2.0 as a service provider endpoints
|
||||
w.MainRouter.Handle("/oauth/authorize", w.ApiHandlerTrustRequester(authorizeOAuthPage)).Methods("GET")
|
||||
w.MainRouter.Handle("/oauth/authorize", w.ApiSessionRequired(authorizeOAuthApp)).Methods("POST")
|
||||
w.MainRouter.Handle("/oauth/deauthorize", w.ApiSessionRequired(deauthorizeOAuthApp)).Methods("POST")
|
||||
w.MainRouter.Handle("/oauth/access_token", w.ApiHandlerTrustRequester(getAccessToken)).Methods("POST")
|
||||
|
||||
// API version independent OAuth as a client endpoints
|
||||
w.MainRouter.Handle("/oauth/{service:[A-Za-z0-9]+}/complete", w.ApiHandler(completeOAuth)).Methods("GET")
|
||||
w.MainRouter.Handle("/oauth/{service:[A-Za-z0-9]+}/login", w.ApiHandler(loginWithOAuth)).Methods("GET")
|
||||
w.MainRouter.Handle("/oauth/{service:[A-Za-z0-9]+}/mobile_login", w.ApiHandler(mobileLoginWithOAuth)).Methods("GET")
|
||||
w.MainRouter.Handle("/oauth/{service:[A-Za-z0-9]+}/signup", w.ApiHandler(signupWithOAuth)).Methods("GET")
|
||||
|
||||
// Old endpoints for backwards compatibility, needed to not break SSO for any old setups
|
||||
w.MainRouter.Handle("/api/v3/oauth/{service:[A-Za-z0-9]+}/complete", w.ApiHandler(completeOAuth)).Methods("GET")
|
||||
w.MainRouter.Handle("/signup/{service:[A-Za-z0-9]+}/complete", w.ApiHandler(completeOAuth)).Methods("GET")
|
||||
w.MainRouter.Handle("/login/{service:[A-Za-z0-9]+}/complete", w.ApiHandler(completeOAuth)).Methods("GET")
|
||||
w.MainRouter.Handle("/api/v4/oauth_test", w.ApiSessionRequired(testHandler)).Methods("GET")
|
||||
}
|
||||
|
||||
func testHandler(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func authorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
authRequest := model.AuthorizeRequestFromJson(r.Body)
|
||||
if authRequest == nil {
|
||||
c.SetInvalidParam("authorize_request")
|
||||
}
|
||||
|
||||
if err := authRequest.IsValid(); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if c.App.Session.IsOAuth {
|
||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||
c.Err.DetailedError += ", attempted access by oauth app"
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("attempt")
|
||||
|
||||
redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.App.Session.UserId, authRequest)
|
||||
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("")
|
||||
|
||||
w.Write([]byte(model.MapToJson(map[string]string{"redirect": redirectUrl})))
|
||||
}
|
||||
|
||||
func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
requestData := model.MapFromJson(r.Body)
|
||||
clientId := requestData["client_id"]
|
||||
|
||||
if len(clientId) != 26 {
|
||||
c.SetInvalidParam("client_id")
|
||||
return
|
||||
}
|
||||
|
||||
err := c.App.DeauthorizeOAuthAppForUser(c.App.Session.UserId, clientId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("success")
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !*c.App.Config().ServiceSettings.EnableOAuthServiceProvider {
|
||||
err := model.NewAppError("authorizeOAuth", "api.oauth.authorize_oauth.disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: r.URL.Query().Get("response_type"),
|
||||
ClientId: r.URL.Query().Get("client_id"),
|
||||
RedirectUri: r.URL.Query().Get("redirect_uri"),
|
||||
Scope: r.URL.Query().Get("scope"),
|
||||
State: r.URL.Query().Get("state"),
|
||||
}
|
||||
|
||||
loginHint := r.URL.Query().Get("login_hint")
|
||||
|
||||
if err := authRequest.IsValid(); err != nil {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
oauthApp, err := c.App.GetOAuthApp(authRequest.ClientId)
|
||||
if err != nil {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
// here we should check if the user is logged in
|
||||
if len(c.App.Session.UserId) == 0 {
|
||||
if loginHint == model.USER_AUTH_SERVICE_SAML {
|
||||
http.Redirect(w, r, c.GetSiteURLHeader()+"/login/sso/saml?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound)
|
||||
} else {
|
||||
http.Redirect(w, r, c.GetSiteURLHeader()+"/login?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !oauthApp.IsValidRedirectURL(authRequest.RedirectUri) {
|
||||
err := model.NewAppError("authorizeOAuthPage", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest)
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
isAuthorized := false
|
||||
|
||||
if _, err := c.App.GetPreferenceByCategoryAndNameForUser(c.App.Session.UserId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, authRequest.ClientId); err == nil {
|
||||
// when we support scopes we should check if the scopes match
|
||||
isAuthorized = true
|
||||
}
|
||||
|
||||
// Automatically allow if the app is trusted
|
||||
if oauthApp.IsTrusted || isAuthorized {
|
||||
redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.App.Session.UserId, authRequest)
|
||||
|
||||
if err != nil {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, redirectUrl, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
w.Header().Set("Content-Security-Policy", "frame-ancestors 'self'")
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-cache, max-age=31556926, public")
|
||||
|
||||
staticDir, _ := fileutils.FindDir(model.CLIENT_DIR)
|
||||
http.ServeFile(w, r, filepath.Join(staticDir, "root.html"))
|
||||
}
|
||||
|
||||
func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
|
||||
code := r.FormValue("code")
|
||||
refreshToken := r.FormValue("refresh_token")
|
||||
|
||||
grantType := r.FormValue("grant_type")
|
||||
switch grantType {
|
||||
case model.ACCESS_TOKEN_GRANT_TYPE:
|
||||
if len(code) == 0 {
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.missing_code.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
case model.REFRESH_TOKEN_GRANT_TYPE:
|
||||
if len(refreshToken) == 0 {
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.missing_refresh_token.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
default:
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_grant.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
clientId := r.FormValue("client_id")
|
||||
if len(clientId) != 26 {
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_client_id.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
secret := r.FormValue("client_secret")
|
||||
if len(secret) == 0 {
|
||||
c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_client_secret.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
redirectUri := r.FormValue("redirect_uri")
|
||||
|
||||
c.LogAudit("attempt")
|
||||
|
||||
accessRsp, err := c.App.GetOAuthAccessTokenForCodeFlow(clientId, grantType, redirectUri, code, secret, refreshToken)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
|
||||
c.LogAudit("success")
|
||||
|
||||
w.Write([]byte(accessRsp.ToJson()))
|
||||
}
|
||||
|
||||
func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireService()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
service := c.Params.Service
|
||||
|
||||
oauthError := r.URL.Query().Get("error")
|
||||
if oauthError == "access_denied" {
|
||||
utils.RenderWebError(c.App.Config(), w, r, http.StatusTemporaryRedirect, url.Values{
|
||||
"type": []string{"oauth_access_denied"},
|
||||
"service": []string{strings.Title(service)},
|
||||
}, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
code := r.URL.Query().Get("code")
|
||||
if len(code) == 0 {
|
||||
utils.RenderWebError(c.App.Config(), w, r, http.StatusTemporaryRedirect, url.Values{
|
||||
"type": []string{"oauth_missing_code"},
|
||||
"service": []string{strings.Title(service)},
|
||||
}, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
state := r.URL.Query().Get("state")
|
||||
|
||||
uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete"
|
||||
|
||||
body, teamId, props, err := c.App.AuthorizeOAuthUser(w, r, service, code, state, uri)
|
||||
|
||||
action := ""
|
||||
if props != nil {
|
||||
action = props["action"]
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err.Translate(c.App.T)
|
||||
mlog.Error(err.Error())
|
||||
if action == model.OAUTH_ACTION_MOBILE {
|
||||
w.Write([]byte(err.ToJson()))
|
||||
} else {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
user, err := c.App.CompleteOAuth(service, body, teamId, props)
|
||||
if err != nil {
|
||||
err.Translate(c.App.T)
|
||||
mlog.Error(err.Error())
|
||||
if action == model.OAUTH_ACTION_MOBILE {
|
||||
w.Write([]byte(err.ToJson()))
|
||||
} else {
|
||||
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var redirectUrl string
|
||||
if action == model.OAUTH_ACTION_EMAIL_TO_SSO {
|
||||
redirectUrl = c.GetSiteURLHeader() + "/login?extra=signin_change"
|
||||
} else if action == model.OAUTH_ACTION_SSO_TO_EMAIL {
|
||||
redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"])
|
||||
} else {
|
||||
session, err := c.App.DoLogin(w, r, user, "")
|
||||
if err != nil {
|
||||
err.Translate(c.App.T)
|
||||
c.Err = err
|
||||
if action == model.OAUTH_ACTION_MOBILE {
|
||||
w.Write([]byte(err.ToJson()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.App.AttachSessionCookies(w, r, session)
|
||||
|
||||
c.App.Session = *session
|
||||
|
||||
if _, ok := props["redirect_to"]; ok {
|
||||
redirectUrl = props["redirect_to"]
|
||||
} else {
|
||||
redirectUrl = c.GetSiteURLHeader()
|
||||
}
|
||||
}
|
||||
|
||||
if action == model.OAUTH_ACTION_MOBILE {
|
||||
ReturnStatusOK(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireService()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
loginHint := r.URL.Query().Get("login_hint")
|
||||
redirectTo := r.URL.Query().Get("redirect_to")
|
||||
|
||||
teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
authUrl, err := c.App.GetOAuthLoginEndpoint(w, r, c.Params.Service, teamId, model.OAUTH_ACTION_LOGIN, redirectTo, loginHint)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, authUrl, http.StatusFound)
|
||||
}
|
||||
|
||||
func mobileLoginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireService()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
authUrl, err := c.App.GetOAuthLoginEndpoint(w, r, c.Params.Service, teamId, model.OAUTH_ACTION_MOBILE, "", "")
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, authUrl, http.StatusFound)
|
||||
}
|
||||
|
||||
func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireService()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !*c.App.Config().TeamSettings.EnableUserCreation {
|
||||
utils.RenderWebError(c.App.Config(), w, r, http.StatusBadRequest, url.Values{
|
||||
"message": []string{utils.T("api.oauth.singup_with_oauth.disabled.app_error")},
|
||||
}, c.App.AsymmetricSigningKey())
|
||||
return
|
||||
}
|
||||
|
||||
teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
authUrl, err := c.App.GetOAuthSignupEndpoint(w, r, c.Params.Service, teamId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, authUrl, http.StatusFound)
|
||||
}
|
||||
795
web/oauth_test.go
Обычный файл
795
web/oauth_test.go
Обычный файл
@@ -0,0 +1,795 @@
|
||||
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOAuthComplete_AccessDenied(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
Params: &Params{
|
||||
Service: "TestService",
|
||||
},
|
||||
}
|
||||
responseWriter := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/signup/TestService/complete?error=access_denied", nil)
|
||||
|
||||
completeOAuth(c, responseWriter, request)
|
||||
|
||||
response := responseWriter.Result()
|
||||
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, response.StatusCode)
|
||||
|
||||
location, _ := url.Parse(response.Header.Get("Location"))
|
||||
assert.Equal(t, "oauth_access_denied", location.Query().Get("type"))
|
||||
assert.Equal(t, "TestService", location.Query().Get("service"))
|
||||
}
|
||||
|
||||
func TestAuthorizeOAuthApp(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
th.Login(ApiClient, th.SystemAdminUser)
|
||||
defer th.TearDown()
|
||||
|
||||
enableOAuth := *th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
oapp := &model.OAuthApp{
|
||||
Name: GenerateTestAppName(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{"https://nowhere.com"},
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
}
|
||||
|
||||
rapp, appErr := th.App.CreateOAuthApp(oapp)
|
||||
CheckNoAppError(t, appErr)
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: rapp.Id,
|
||||
RedirectUri: rapp.CallbackUrls[0],
|
||||
Scope: "",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
// Test auth code flow
|
||||
ruri, resp := ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(ruri) == 0 {
|
||||
t.Fatal("redirect url should be set")
|
||||
}
|
||||
|
||||
ru, _ := url.Parse(ruri)
|
||||
if ru == nil {
|
||||
t.Fatal("redirect url unparseable")
|
||||
} else {
|
||||
if len(ru.Query().Get("code")) == 0 {
|
||||
t.Fatal("authorization code not returned")
|
||||
}
|
||||
if ru.Query().Get("state") != authRequest.State {
|
||||
t.Fatal("returned state doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
// Test implicit flow
|
||||
authRequest.ResponseType = model.IMPLICIT_RESPONSE_TYPE
|
||||
ruri, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
require.False(t, len(ruri) == 0, "redirect url should be set")
|
||||
|
||||
ru, _ = url.Parse(ruri)
|
||||
require.NotNil(t, ru, "redirect url unparseable")
|
||||
values, err := url.ParseQuery(ru.Fragment)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, len(values.Get("access_token")) == 0, "access_token not returned")
|
||||
assert.Equal(t, authRequest.State, values.Get("state"), "returned state doesn't match")
|
||||
|
||||
oldToken := ApiClient.AuthToken
|
||||
ApiClient.AuthToken = values.Get("access_token")
|
||||
_, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
ApiClient.AuthToken = oldToken
|
||||
|
||||
authRequest.RedirectUri = ""
|
||||
_, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.RedirectUri = "http://somewhereelse.com"
|
||||
_, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.RedirectUri = rapp.CallbackUrls[0]
|
||||
authRequest.ResponseType = ""
|
||||
_, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.ResponseType = model.AUTHCODE_RESPONSE_TYPE
|
||||
authRequest.ClientId = ""
|
||||
_, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.ClientId = model.NewId()
|
||||
_, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestDeauthorizeOAuthApp(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
th.Login(ApiClient, th.SystemAdminUser)
|
||||
defer th.TearDown()
|
||||
|
||||
enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
oapp := &model.OAuthApp{
|
||||
Name: GenerateTestAppName(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{"https://nowhere.com"},
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
}
|
||||
|
||||
rapp, appErr := th.App.CreateOAuthApp(oapp)
|
||||
CheckNoAppError(t, appErr)
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: rapp.Id,
|
||||
RedirectUri: rapp.CallbackUrls[0],
|
||||
Scope: "",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
_, resp := ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
pass, resp := ApiClient.DeauthorizeOAuthApp(rapp.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if !pass {
|
||||
t.Fatal("should have passed")
|
||||
}
|
||||
|
||||
_, resp = ApiClient.DeauthorizeOAuthApp("junk")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
_, resp = ApiClient.DeauthorizeOAuthApp(model.NewId())
|
||||
CheckNoError(t, resp)
|
||||
|
||||
th.Logout(ApiClient)
|
||||
_, resp = ApiClient.DeauthorizeOAuthApp(rapp.Id)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestOAuthAccessToken(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := Setup().InitBasic()
|
||||
th.Login(ApiClient, th.SystemAdminUser)
|
||||
defer th.TearDown()
|
||||
|
||||
enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID)
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||
|
||||
oauthApp := &model.OAuthApp{
|
||||
Name: "TestApp5" + model.NewId(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{"https://nowhere.com"},
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
}
|
||||
oauthApp, appErr := th.App.CreateOAuthApp(oauthApp)
|
||||
CheckNoAppError(t, appErr)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
|
||||
data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{"12345678901234567890123456"}, "client_secret": []string{"12345678901234567890123456"}, "code": []string{"junk"}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}}
|
||||
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Log(resp.StatusCode)
|
||||
t.Fatal("should have failed - oauth providing turned off")
|
||||
}
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: oauthApp.Id,
|
||||
RedirectUri: oauthApp.CallbackUrls[0],
|
||||
Scope: "all",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
redirect, resp := ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ := url.Parse(redirect)
|
||||
|
||||
ApiClient.Logout()
|
||||
|
||||
data = url.Values{"grant_type": []string{"junk"}, "client_id": []string{oauthApp.Id}, "client_secret": []string{oauthApp.ClientSecret}, "code": []string{rurl.Query().Get("code")}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}}
|
||||
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad grant type")
|
||||
}
|
||||
|
||||
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", "")
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - missing client id")
|
||||
}
|
||||
data.Set("client_id", "junk")
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad client id")
|
||||
}
|
||||
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", "")
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - missing client secret")
|
||||
}
|
||||
|
||||
data.Set("client_secret", "junk")
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad client secret")
|
||||
}
|
||||
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("code", "")
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - missing code")
|
||||
}
|
||||
|
||||
data.Set("code", "junk")
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad code")
|
||||
}
|
||||
|
||||
data.Set("code", rurl.Query().Get("code"))
|
||||
data.Set("redirect_uri", "junk")
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - non-matching redirect uri")
|
||||
}
|
||||
|
||||
// reset data for successful request
|
||||
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("code", rurl.Query().Get("code"))
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
|
||||
token := ""
|
||||
refreshToken := ""
|
||||
if rsp, resp := ApiClient.GetOAuthAccessToken(data); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(rsp.AccessToken) == 0 {
|
||||
t.Fatal("access token not returned")
|
||||
} else if len(rsp.RefreshToken) == 0 {
|
||||
t.Fatal("refresh token not returned")
|
||||
} else {
|
||||
token = rsp.AccessToken
|
||||
refreshToken = rsp.RefreshToken
|
||||
}
|
||||
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
|
||||
t.Fatal("access token type incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := ApiClient.DoApiGet("/oauth_test", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ApiClient.SetOAuthToken("")
|
||||
if _, err := ApiClient.DoApiGet("/oauth_test", ""); err == nil {
|
||||
t.Fatal("should have failed - no access token provided")
|
||||
}
|
||||
|
||||
ApiClient.SetOAuthToken("badtoken")
|
||||
if _, err := ApiClient.DoApiGet("/oauth_test", ""); err == nil {
|
||||
t.Fatal("should have failed - bad token provided")
|
||||
}
|
||||
|
||||
ApiClient.SetOAuthToken(token)
|
||||
if _, err := ApiClient.DoApiGet("/oauth_test", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - tried to reuse auth code")
|
||||
}
|
||||
|
||||
data.Set("grant_type", model.REFRESH_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("refresh_token", "")
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
data.Del("code")
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("Should have failed - refresh token empty")
|
||||
}
|
||||
|
||||
data.Set("refresh_token", refreshToken)
|
||||
if rsp, resp := ApiClient.GetOAuthAccessToken(data); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(rsp.AccessToken) == 0 {
|
||||
t.Fatal("access token not returned")
|
||||
} else if len(rsp.RefreshToken) == 0 {
|
||||
t.Fatal("refresh token not returned")
|
||||
} else if rsp.RefreshToken == refreshToken {
|
||||
t.Fatal("refresh token did not update")
|
||||
}
|
||||
|
||||
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
|
||||
t.Fatal("access token type incorrect")
|
||||
}
|
||||
ApiClient.SetOAuthToken(rsp.AccessToken)
|
||||
if _, err := ApiClient.DoApiGet("/oauth_test", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data.Set("refresh_token", rsp.RefreshToken)
|
||||
}
|
||||
|
||||
if rsp, resp := ApiClient.GetOAuthAccessToken(data); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(rsp.AccessToken) == 0 {
|
||||
t.Fatal("access token not returned")
|
||||
} else if len(rsp.RefreshToken) == 0 {
|
||||
t.Fatal("refresh token not returned")
|
||||
} else if rsp.RefreshToken == refreshToken {
|
||||
t.Fatal("refresh token did not update")
|
||||
}
|
||||
|
||||
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
|
||||
t.Fatal("access token type incorrect")
|
||||
}
|
||||
ApiClient.SetOAuthToken(rsp.AccessToken)
|
||||
if _, err := ApiClient.DoApiGet("/oauth_test", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
authData := &model.AuthData{ClientId: oauthApp.Id, RedirectUri: oauthApp.CallbackUrls[0], UserId: th.BasicUser.Id, Code: model.NewId(), ExpiresIn: -1}
|
||||
_, err := th.App.Srv.Store.OAuth().SaveAuthData(authData)
|
||||
require.Nil(t, err)
|
||||
|
||||
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
data.Set("code", authData.Code)
|
||||
data.Del("refresh_token")
|
||||
if _, resp := ApiClient.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("Should have failed - code is expired")
|
||||
}
|
||||
|
||||
ApiClient.ClearOAuthToken()
|
||||
}
|
||||
|
||||
func TestOAuthComplete(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := Setup().InitBasic()
|
||||
th.Login(ApiClient, th.SystemAdminUser)
|
||||
defer th.TearDown()
|
||||
|
||||
gitLabSettingsEnable := th.App.Config().GitLabSettings.Enable
|
||||
gitLabSettingsAuthEndpoint := th.App.Config().GitLabSettings.AuthEndpoint
|
||||
gitLabSettingsId := th.App.Config().GitLabSettings.Id
|
||||
gitLabSettingsSecret := th.App.Config().GitLabSettings.Secret
|
||||
gitLabSettingsTokenEndpoint := th.App.Config().GitLabSettings.TokenEndpoint
|
||||
gitLabSettingsUserApiEndpoint := th.App.Config().GitLabSettings.UserApiEndpoint
|
||||
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Enable = gitLabSettingsEnable })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = gitLabSettingsAuthEndpoint })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = gitLabSettingsId })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Secret = gitLabSettingsSecret })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.TokenEndpoint = gitLabSettingsTokenEndpoint })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.UserApiEndpoint = gitLabSettingsUserApiEndpoint })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
|
||||
}()
|
||||
|
||||
r, err := HttpGet(ApiClient.Url+"/login/gitlab/complete?code=123", ApiClient.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Enable = true })
|
||||
r, err = HttpGet(ApiClient.Url+"/login/gitlab/complete?code=123&state=!#$#F@#Yˆ&~ñ", ApiClient.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.AuthEndpoint = ApiClient.Url + "/oauth/authorize" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Id = model.NewId() })
|
||||
|
||||
stateProps := map[string]string{}
|
||||
stateProps["action"] = model.OAUTH_ACTION_LOGIN
|
||||
stateProps["team_id"] = th.BasicTeam.Id
|
||||
stateProps["redirect_to"] = *th.App.Config().GitLabSettings.AuthEndpoint
|
||||
|
||||
state := base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
r, err = HttpGet(ApiClient.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), ApiClient.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
stateProps["hash"] = utils.HashSha256(*th.App.Config().GitLabSettings.Id)
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
r, err = HttpGet(ApiClient.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), ApiClient.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
// We are going to use mattermost as the provider emulating gitlab
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID)
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||
|
||||
oauthApp := &model.OAuthApp{
|
||||
Name: "TestApp5" + model.NewId(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{
|
||||
ApiClient.Url + "/signup/" + model.SERVICE_GITLAB + "/complete",
|
||||
ApiClient.Url + "/login/" + model.SERVICE_GITLAB + "/complete",
|
||||
},
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
IsTrusted: true,
|
||||
}
|
||||
oauthApp, appErr := th.App.CreateOAuthApp(oauthApp)
|
||||
CheckNoAppError(t, appErr)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Id = oauthApp.Id })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Secret = oauthApp.ClientSecret })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.AuthEndpoint = ApiClient.Url + "/oauth/authorize" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.TokenEndpoint = ApiClient.Url + "/oauth/access_token" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.UserApiEndpoint = ApiClient.ApiUrl + "/users/me" })
|
||||
|
||||
provider := &MattermostTestProvider{}
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: oauthApp.Id,
|
||||
RedirectUri: oauthApp.CallbackUrls[0],
|
||||
Scope: "all",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
redirect, resp := ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ := url.Parse(redirect)
|
||||
|
||||
code := rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO
|
||||
delete(stateProps, "team_id")
|
||||
stateProps["redirect_to"] = *th.App.Config().GitLabSettings.AuthEndpoint
|
||||
stateProps["hash"] = utils.HashSha256(*th.App.Config().GitLabSettings.Id)
|
||||
stateProps["redirect_to"] = "/oauth/authorize"
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(ApiClient.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), ApiClient.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
einterfaces.RegisterOauthProvider(model.SERVICE_GITLAB, provider)
|
||||
|
||||
redirect, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
if r, err := HttpGet(ApiClient.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), ApiClient.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
if _, err := th.App.Srv.Store.User().UpdateAuthData(
|
||||
th.BasicUser.Id, model.SERVICE_GITLAB, &th.BasicUser.Email, th.BasicUser.Email, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
redirect, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAUTH_ACTION_LOGIN
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(ApiClient.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), ApiClient.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
redirect, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
delete(stateProps, "action")
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(ApiClient.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), ApiClient.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
redirect, resp = ApiClient.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAUTH_ACTION_SIGNUP
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(ApiClient.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), ApiClient.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
}
|
||||
|
||||
func HttpGet(url string, httpClient *http.Client, authToken string, followRedirect bool) (*http.Response, *model.AppError) {
|
||||
rq, _ := http.NewRequest("GET", url, nil)
|
||||
rq.Close = true
|
||||
|
||||
if len(authToken) > 0 {
|
||||
rq.Header.Set(model.HEADER_AUTH, authToken)
|
||||
}
|
||||
|
||||
if !followRedirect {
|
||||
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
}
|
||||
|
||||
if rp, err := httpClient.Do(rq); err != nil {
|
||||
return nil, model.NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)
|
||||
} else if rp.StatusCode == 304 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode == 307 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode >= 300 {
|
||||
defer closeBody(rp)
|
||||
return rp, model.AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
return rp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func closeBody(r *http.Response) {
|
||||
if r != nil && r.Body != nil {
|
||||
ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type MattermostTestProvider struct {
|
||||
}
|
||||
|
||||
func (m *MattermostTestProvider) GetUserFromJson(data io.Reader) *model.User {
|
||||
user := model.UserFromJson(data)
|
||||
user.AuthData = &user.Email
|
||||
return user
|
||||
}
|
||||
|
||||
func GenerateTestAppName() string {
|
||||
return "fakeoauthapp" + model.NewRandomString(10)
|
||||
}
|
||||
|
||||
func CheckNoAppError(t *testing.T, err *model.AppError) {
|
||||
t.Helper()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %q", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func CheckNoError(t *testing.T, resp *model.Response) {
|
||||
t.Helper()
|
||||
|
||||
if resp.Error != nil {
|
||||
t.Fatalf("Expected no error, got %q", resp.Error.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func checkHTTPStatus(t *testing.T, resp *model.Response, expectedStatus int, expectError bool) {
|
||||
t.Helper()
|
||||
|
||||
switch {
|
||||
case resp == nil:
|
||||
t.Fatalf("Unexpected nil response, expected http:%v, expectError:%v)", expectedStatus, expectError)
|
||||
|
||||
case expectError && resp.Error == nil:
|
||||
t.Fatalf("Expected a non-nil error and http status:%v, got nil, %v", expectedStatus, resp.StatusCode)
|
||||
|
||||
case !expectError && resp.Error != nil:
|
||||
t.Fatalf("Expected no error and http status:%v, got %q, http:%v", expectedStatus, resp.Error, resp.StatusCode)
|
||||
|
||||
case resp.StatusCode != expectedStatus:
|
||||
t.Fatalf("Expected http status:%v, got %v (err: %q)", expectedStatus, resp.StatusCode, resp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckForbiddenStatus(t *testing.T, resp *model.Response) {
|
||||
t.Helper()
|
||||
checkHTTPStatus(t, resp, http.StatusForbidden, true)
|
||||
}
|
||||
|
||||
func CheckUnauthorizedStatus(t *testing.T, resp *model.Response) {
|
||||
t.Helper()
|
||||
checkHTTPStatus(t, resp, http.StatusUnauthorized, true)
|
||||
}
|
||||
|
||||
func CheckNotFoundStatus(t *testing.T, resp *model.Response) {
|
||||
t.Helper()
|
||||
checkHTTPStatus(t, resp, http.StatusNotFound, true)
|
||||
}
|
||||
|
||||
func CheckBadRequestStatus(t *testing.T, resp *model.Response) {
|
||||
t.Helper()
|
||||
checkHTTPStatus(t, resp, http.StatusBadRequest, true)
|
||||
}
|
||||
|
||||
func (th *TestHelper) Login(client *model.Client4, user *model.User) {
|
||||
session := &model.Session{
|
||||
UserId: user.Id,
|
||||
Roles: user.GetRawRoles(),
|
||||
IsOAuth: false,
|
||||
}
|
||||
session, _ = th.App.CreateSession(session)
|
||||
client.AuthToken = session.Token
|
||||
client.AuthType = model.HEADER_BEARER
|
||||
}
|
||||
|
||||
func (th *TestHelper) Logout(client *model.Client4) {
|
||||
client.AuthToken = ""
|
||||
}
|
||||
|
||||
func (th *TestHelper) SaveDefaultRolePermissions() map[string][]string {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
results := make(map[string][]string)
|
||||
|
||||
for _, roleName := range []string{
|
||||
"system_user",
|
||||
"system_admin",
|
||||
"team_user",
|
||||
"team_admin",
|
||||
"channel_user",
|
||||
"channel_admin",
|
||||
} {
|
||||
role, err1 := th.App.GetRoleByName(roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
results[roleName] = role.Permissions
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
return results
|
||||
}
|
||||
|
||||
func (th *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
for roleName, permissions := range data {
|
||||
role, err1 := th.App.GetRoleByName(roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
if strings.Join(role.Permissions, " ") == strings.Join(permissions, " ") {
|
||||
continue
|
||||
}
|
||||
|
||||
role.Permissions = permissions
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err2)
|
||||
}
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
// func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) {
|
||||
// utils.DisableDebugLogForTest()
|
||||
|
||||
// role, err1 := th.App.GetRoleByName(roleName)
|
||||
// if err1 != nil {
|
||||
// utils.EnableDebugLogForTest()
|
||||
// panic(err1)
|
||||
// }
|
||||
|
||||
// var newPermissions []string
|
||||
// for _, p := range role.Permissions {
|
||||
// if p != permission {
|
||||
// newPermissions = append(newPermissions, p)
|
||||
// }
|
||||
// }
|
||||
|
||||
// if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
|
||||
// utils.EnableDebugLogForTest()
|
||||
// return
|
||||
// }
|
||||
|
||||
// role.Permissions = newPermissions
|
||||
|
||||
// _, err2 := th.App.UpdateRole(role)
|
||||
// if err2 != nil {
|
||||
// utils.EnableDebugLogForTest()
|
||||
// panic(err2)
|
||||
// }
|
||||
|
||||
// utils.EnableDebugLogForTest()
|
||||
// }
|
||||
|
||||
func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
role, err1 := th.App.GetRoleByName(roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
for _, existingPermission := range role.Permissions {
|
||||
if existingPermission == permission {
|
||||
utils.EnableDebugLogForTest()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
role.Permissions = append(role.Permissions, permission)
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
@@ -33,6 +33,7 @@ func New(config configservice.ConfigService, globalOptions app.AppOptionCreator,
|
||||
MainRouter: root,
|
||||
}
|
||||
|
||||
web.InitOAuth()
|
||||
web.InitWebhooks()
|
||||
web.InitSaml()
|
||||
web.InitStatic()
|
||||
|
||||
Ссылка в новой задаче
Block a user