Implement v4 endpoints for OAuth (#6040)

* Implement POST /oauth/apps endpoint for APIv4

* Implement GET /oauth/apps endpoint for APIv4

* Implement GET /oauth/apps/{app_id} and /oauth/apps/{app_id}/info endpoints for APIv4

* Refactor API version independent oauth endpoints

* Implement DELETE /oauth/apps/{app_id} endpoint for APIv4

* Implement /oauth/apps/{app_id}/regen_secret endpoint for APIv4

* Implement GET /user/{user_id}/oauth/apps/authorized endpoint for APIv4

* Implement POST /oauth/deauthorize endpoint
Этот коммит содержится в:
Joram Wilander
2017-04-20 09:55:02 -04:00
коммит произвёл GitHub
родитель 1a0f8d1b3c
Коммит be9624e2ad
17 изменённых файлов: 1429 добавлений и 253 удалений

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

@@ -75,6 +75,7 @@ func Setup() *TestHelper {
InitRouter() InitRouter()
wsapi.InitRouter() wsapi.InitRouter()
app.StartServer() app.StartServer()
api4.InitApi(false)
InitApi() InitApi()
wsapi.InitApi() wsapi.InitApi()
utils.EnableDebugLogForTest() utils.EnableDebugLogForTest()

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

@@ -242,7 +242,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if c.Err.StatusCode == http.StatusUnauthorized { if c.Err.StatusCode == http.StatusUnauthorized {
http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect) http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
} else { } else {
RenderWebError(c.Err, w, r) utils.RenderWebError(c.Err, w, r)
} }
} }
@@ -421,31 +421,6 @@ func IsApiCall(r *http.Request) bool {
return strings.Index(r.URL.Path, "/api/") == 0 return strings.Index(r.URL.Path, "/api/") == 0
} }
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
T, _ := utils.GetTranslationsAndLocale(w, r)
title := T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
message := err.Message
details := err.DetailedError
link := "/"
linkMessage := T("api.templates.error.link")
status := http.StatusTemporaryRedirect
if err.StatusCode != http.StatusInternalServerError {
status = err.StatusCode
}
http.Redirect(
w,
r,
"/error?title="+url.QueryEscape(title)+
"&message="+url.QueryEscape(message)+
"&details="+url.QueryEscape(details)+
"&link="+url.QueryEscape(link)+
"&linkmessage="+url.QueryEscape(linkMessage),
status)
}
func Handle404(w http.ResponseWriter, r *http.Request) { func Handle404(w http.ResponseWriter, r *http.Request) {
err := model.NewLocAppError("Handle404", "api.context.404.app_error", nil, "") err := model.NewLocAppError("Handle404", "api.context.404.app_error", nil, "")
err.Translate(utils.T) err.Translate(utils.T)
@@ -458,7 +433,7 @@ func Handle404(w http.ResponseWriter, r *http.Request) {
err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'. Typo? are you missing a team_id or user_id as part of the url?" err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'. Typo? are you missing a team_id or user_id as part of the url?"
w.Write([]byte(err.ToJson())) w.Write([]byte(err.ToJson()))
} else { } else {
RenderWebError(err, w, r) utils.RenderWebError(err, w, r)
} }
} }

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

@@ -5,8 +5,6 @@ package api
import ( import (
"net/http" "net/http"
"net/url"
"strings"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@@ -26,17 +24,8 @@ func InitOAuth() {
BaseRoutes.OAuth.Handle("/delete", ApiUserRequired(deleteOAuthApp)).Methods("POST") BaseRoutes.OAuth.Handle("/delete", ApiUserRequired(deleteOAuthApp)).Methods("POST")
BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/deauthorize", ApiUserRequired(deauthorizeOAuthApp)).Methods("POST") BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/deauthorize", ApiUserRequired(deauthorizeOAuthApp)).Methods("POST")
BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/regen_secret", ApiUserRequired(regenerateOAuthSecret)).Methods("POST") BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/regen_secret", ApiUserRequired(regenerateOAuthSecret)).Methods("POST")
BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/login", AppHandlerIndependent(loginWithOAuth)).Methods("GET") BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/login", AppHandlerIndependent(loginWithOAuth)).Methods("GET")
BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/signup", AppHandlerIndependent(signupWithOAuth)).Methods("GET") BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/signup", AppHandlerIndependent(signupWithOAuth)).Methods("GET")
BaseRoutes.Root.Handle("/oauth/authorize", AppHandlerTrustRequester(authorizeOAuth)).Methods("GET")
BaseRoutes.Root.Handle("/oauth/access_token", ApiAppHandlerTrustRequester(getAccessToken)).Methods("POST")
// Handle all the old routes, to be later removed
BaseRoutes.Root.Handle("/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
BaseRoutes.Root.Handle("/signup/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
BaseRoutes.Root.Handle("/login/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET")
} }
func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -126,7 +115,15 @@ func allowOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("attempt") c.LogAudit("attempt")
redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, responseType, clientId, redirectUri, scope, state) authRequest := &model.AuthorizeRequest{
ResponseType: responseType,
ClientId: clientId,
RedirectUri: redirectUri,
Scope: scope,
State: state,
}
redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, authRequest)
if err != nil { if err != nil {
c.Err = err c.Err = err
@@ -148,167 +145,6 @@ func getAuthorizedApps(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.OAuthAppListToJson(apps))) w.Write([]byte(model.OAuthAppListToJson(apps)))
} }
func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
service := params["service"]
code := r.URL.Query().Get("code")
if len(code) == 0 {
c.Err = model.NewLocAppError("completeOAuth", "api.oauth.complete_oauth.missing_code.app_error", map[string]interface{}{"service": strings.Title(service)}, "URL: "+r.URL.String())
return
}
state := r.URL.Query().Get("state")
uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete"
body, teamId, props, err := app.AuthorizeOAuthUser(service, code, state, uri)
if err != nil {
c.Err = err
return
}
user, err := app.CompleteOAuth(service, body, teamId, props)
if err != nil {
c.Err = err
return
}
action := props["action"]
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 {
doLogin(c, w, r, user, "")
if c.Err != nil {
return
}
redirectUrl = c.GetSiteURLHeader()
}
http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect)
}
func authorizeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
c.Err = model.NewLocAppError("authorizeOAuth", "api.oauth.authorize_oauth.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
responseType := r.URL.Query().Get("response_type")
clientId := r.URL.Query().Get("client_id")
redirect := r.URL.Query().Get("redirect_uri")
scope := r.URL.Query().Get("scope")
state := r.URL.Query().Get("state")
if len(scope) == 0 {
scope = model.DEFAULT_SCOPE
}
if len(responseType) == 0 || len(clientId) == 0 || len(redirect) == 0 {
c.Err = model.NewLocAppError("authorizeOAuth", "api.oauth.authorize_oauth.missing.app_error", nil, "")
return
}
var oauthApp *model.OAuthApp
if result := <-app.Srv.Store.OAuth().GetApp(clientId); result.Err != nil {
c.Err = result.Err
return
} else {
oauthApp = result.Data.(*model.OAuthApp)
}
// here we should check if the user is logged in
if len(c.Session.UserId) == 0 {
http.Redirect(w, r, c.GetSiteURLHeader()+"/login?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound)
return
}
isAuthorized := false
if result := <-app.Srv.Store.Preference().Get(c.Session.UserId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, clientId); result.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 := app.AllowOAuthAppAccessToUser(c.Session.UserId, model.AUTHCODE_RESPONSE_TYPE, clientId, redirect, scope, state)
if err != nil {
c.Err = err
return
}
http.Redirect(w, r, redirectUrl, http.StatusFound)
return
}
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Cache-Control", "no-cache, max-age=31556926, public")
http.ServeFile(w, r, utils.FindDir(model.CLIENT_DIR)+"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.NewLocAppError("getAccessToken", "api.oauth.get_access_token.missing_code.app_error", nil, "")
return
}
case model.REFRESH_TOKEN_GRANT_TYPE:
if len(refreshToken) == 0 {
c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.missing_refresh_token.app_error", nil, "")
return
}
default:
c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.bad_grant.app_error", nil, "")
return
}
clientId := r.FormValue("client_id")
if len(clientId) != 26 {
c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.bad_client_id.app_error", nil, "")
return
}
secret := r.FormValue("client_secret")
if len(secret) == 0 {
c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.bad_client_secret.app_error", nil, "")
return
}
redirectUri := r.FormValue("redirect_uri")
c.LogAudit("attempt")
accessRsp, err := app.GetOAuthAccessToken(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 loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r) params := mux.Vars(r)
service := params["service"] service := params["service"]

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

@@ -28,7 +28,6 @@ func TestOAuthRegisterApp(t *testing.T) {
if _, err := Client.RegisterApp(oauthApp); err == nil { if _, err := Client.RegisterApp(oauthApp); err == nil {
t.Fatal("should have failed - oauth providing turned off") t.Fatal("should have failed - oauth providing turned off")
} }
} }
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true

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

@@ -64,8 +64,10 @@ type Routes struct {
OutgoingHooks *mux.Router // 'api/v4/hooks/outgoing' OutgoingHooks *mux.Router // 'api/v4/hooks/outgoing'
OutgoingHook *mux.Router // 'api/v4/hooks/outgoing/{hook_id:[A-Za-z0-9]+}' OutgoingHook *mux.Router // 'api/v4/hooks/outgoing/{hook_id:[A-Za-z0-9]+}'
Admin *mux.Router // 'api/v4/admin' OAuth *mux.Router // 'api/v4/oauth'
OAuth *mux.Router // 'api/v4/oauth' OAuthApps *mux.Router // 'api/v4/oauth/apps'
OAuthApp *mux.Router // 'api/v4/oauth/apps/{app_id:[A-Za-z0-9]+}'
SAML *mux.Router // 'api/v4/saml' SAML *mux.Router // 'api/v4/saml'
Compliance *mux.Router // 'api/v4/compliance' Compliance *mux.Router // 'api/v4/compliance'
Cluster *mux.Router // 'api/v4/cluster' Cluster *mux.Router // 'api/v4/cluster'
@@ -146,8 +148,11 @@ func InitApi(full bool) {
BaseRoutes.OutgoingHook = BaseRoutes.OutgoingHooks.PathPrefix("/{hook_id:[A-Za-z0-9]+}").Subrouter() BaseRoutes.OutgoingHook = BaseRoutes.OutgoingHooks.PathPrefix("/{hook_id:[A-Za-z0-9]+}").Subrouter()
BaseRoutes.SAML = BaseRoutes.ApiRoot.PathPrefix("/saml").Subrouter() BaseRoutes.SAML = BaseRoutes.ApiRoot.PathPrefix("/saml").Subrouter()
BaseRoutes.OAuth = BaseRoutes.ApiRoot.PathPrefix("/oauth").Subrouter() BaseRoutes.OAuth = BaseRoutes.ApiRoot.PathPrefix("/oauth").Subrouter()
BaseRoutes.Admin = BaseRoutes.ApiRoot.PathPrefix("/admin").Subrouter() BaseRoutes.OAuthApps = BaseRoutes.OAuth.PathPrefix("/apps").Subrouter()
BaseRoutes.OAuthApp = BaseRoutes.OAuthApps.PathPrefix("/{app_id:[A-Za-z0-9]+}").Subrouter()
BaseRoutes.Compliance = BaseRoutes.ApiRoot.PathPrefix("/compliance").Subrouter() BaseRoutes.Compliance = BaseRoutes.ApiRoot.PathPrefix("/compliance").Subrouter()
BaseRoutes.Cluster = BaseRoutes.ApiRoot.PathPrefix("/cluster").Subrouter() BaseRoutes.Cluster = BaseRoutes.ApiRoot.PathPrefix("/cluster").Subrouter()
BaseRoutes.LDAP = BaseRoutes.ApiRoot.PathPrefix("/ldap").Subrouter() BaseRoutes.LDAP = BaseRoutes.ApiRoot.PathPrefix("/ldap").Subrouter()
@@ -180,6 +185,7 @@ func InitApi(full bool) {
InitStatus() InitStatus()
InitWebSocket() InitWebSocket()
InitEmoji() InitEmoji()
InitOAuth()
InitReaction() InitReaction()
InitWebrtc() InitWebrtc()

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

@@ -12,6 +12,7 @@ import (
"runtime/debug" "runtime/debug"
"strconv" "strconv"
"strings" "strings"
"sync"
"testing" "testing"
"time" "time"
@@ -107,31 +108,57 @@ func Setup() *TestHelper {
func TearDown() { func TearDown() {
utils.DisableDebugLogForTest() utils.DisableDebugLogForTest()
options := map[string]bool{} var wg sync.WaitGroup
options[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true wg.Add(3)
if result := <-app.Srv.Store.User().Search("", "fakeuser", options); result.Err != nil {
l4g.Error("Error tearing down test users")
} else {
users := result.Data.([]*model.User)
for _, u := range users { go func() {
if err := app.PermanentDeleteUser(u); err != nil { defer wg.Done()
l4g.Error(err.Error()) options := map[string]bool{}
options[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true
if result := <-app.Srv.Store.User().Search("", "fakeuser", options); result.Err != nil {
l4g.Error("Error tearing down test users")
} else {
users := result.Data.([]*model.User)
for _, u := range users {
if err := app.PermanentDeleteUser(u); err != nil {
l4g.Error(err.Error())
}
} }
} }
} }()
if result := <-app.Srv.Store.Team().SearchByName("faketeam"); result.Err != nil { go func() {
l4g.Error("Error tearing down test teams") defer wg.Done()
} else { if result := <-app.Srv.Store.Team().SearchByName("faketeam"); result.Err != nil {
teams := result.Data.([]*model.Team) l4g.Error("Error tearing down test teams")
} else {
teams := result.Data.([]*model.Team)
for _, t := range teams { for _, t := range teams {
if err := app.PermanentDeleteTeam(t); err != nil { if err := app.PermanentDeleteTeam(t); err != nil {
l4g.Error(err.Error()) l4g.Error(err.Error())
}
} }
} }
} }()
go func() {
defer wg.Done()
if result := <-app.Srv.Store.OAuth().GetApps(0, 1000); result.Err != nil {
l4g.Error("Error tearing down test oauth apps")
} else {
apps := result.Data.([]*model.OAuthApp)
for _, a := range apps {
if strings.HasPrefix(a.Name, "fakeoauthapp") {
<-app.Srv.Store.OAuth().DeleteApp(a.Id)
}
}
}
}()
wg.Wait()
utils.EnableDebugLogForTest() utils.EnableDebugLogForTest()
} }
@@ -378,7 +405,7 @@ func GenerateTestEmail() string {
} }
func GenerateTestUsername() string { func GenerateTestUsername() string {
return "fakeuser" + model.NewRandomString(13) return "fakeuser" + model.NewRandomString(10)
} }
func GenerateTestTeamName() string { func GenerateTestTeamName() string {
@@ -389,6 +416,10 @@ func GenerateTestChannelName() string {
return "fakechannel" + model.NewRandomString(10) return "fakechannel" + model.NewRandomString(10)
} }
func GenerateTestAppName() string {
return "fakeoauthapp" + model.NewRandomString(10)
}
func GenerateTestId() string { func GenerateTestId() string {
return model.NewId() return model.NewId()
} }

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

@@ -382,6 +382,17 @@ func (c *Context) RequirePostId() *Context {
return c return c
} }
func (c *Context) RequireAppId() *Context {
if c.Err != nil {
return c
}
if len(c.Params.AppId) != 26 {
c.SetInvalidUrlParam("app_id")
}
return c
}
func (c *Context) RequireFileId() *Context { func (c *Context) RequireFileId() *Context {
if c.Err != nil { if c.Err != nil {
return c return c
@@ -464,6 +475,18 @@ func (c *Context) RequireCategory() *Context {
return c return c
} }
func (c *Context) RequireService() *Context {
if c.Err != nil {
return c
}
if len(c.Params.Service) == 0 {
c.SetInvalidUrlParam("service")
}
return c
}
func (c *Context) RequirePreferenceName() *Context { func (c *Context) RequirePreferenceName() *Context {
if c.Err != nil { if c.Err != nil {
return c return c

481
api4/oauth.go Обычный файл
Просмотреть файл

@@ -0,0 +1,481 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
import (
"net/http"
"net/url"
"strings"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/app"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
func InitOAuth() {
l4g.Debug(utils.T("api.oauth.init.debug"))
BaseRoutes.OAuthApps.Handle("", ApiSessionRequired(createOAuthApp)).Methods("POST")
BaseRoutes.OAuthApps.Handle("", ApiSessionRequired(getOAuthApps)).Methods("GET")
BaseRoutes.OAuthApp.Handle("", ApiSessionRequired(getOAuthApp)).Methods("GET")
BaseRoutes.OAuthApp.Handle("/info", ApiSessionRequired(getOAuthAppInfo)).Methods("GET")
BaseRoutes.OAuthApp.Handle("", ApiSessionRequired(deleteOAuthApp)).Methods("DELETE")
BaseRoutes.OAuthApp.Handle("/regen_secret", ApiSessionRequired(regenerateOAuthAppSecret)).Methods("POST")
BaseRoutes.User.Handle("/oauth/apps/authorized", ApiSessionRequired(getAuthorizedOAuthApps)).Methods("GET")
// API version independent OAuth 2.0 as a service provider endpoints
BaseRoutes.Root.Handle("/oauth/authorize", ApiHandlerTrustRequester(authorizeOAuthPage)).Methods("GET")
BaseRoutes.Root.Handle("/oauth/authorize", ApiSessionRequired(authorizeOAuthApp)).Methods("POST")
BaseRoutes.Root.Handle("/oauth/deauthorize", ApiSessionRequired(deauthorizeOAuthApp)).Methods("POST")
BaseRoutes.Root.Handle("/oauth/access_token", ApiHandlerTrustRequester(getAccessToken)).Methods("POST")
// API version independent OAuth as a client endpoints
BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/complete", ApiHandler(completeOAuth)).Methods("GET")
BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/login", ApiHandler(loginWithOAuth)).Methods("GET")
BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/signup", ApiHandler(signupWithOAuth)).Methods("GET")
// Old endpoints for backwards compatibility, needed to not break SSO for any old setups
BaseRoutes.Root.Handle("/api/v3/oauth/{service:[A-Za-z0-9]+}/complete", ApiHandler(completeOAuth)).Methods("GET")
BaseRoutes.Root.Handle("/signup/{service:[A-Za-z0-9]+}/complete", ApiHandler(completeOAuth)).Methods("GET")
BaseRoutes.Root.Handle("/login/{service:[A-Za-z0-9]+}/complete", ApiHandler(completeOAuth)).Methods("GET")
}
func createOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
oauthApp := model.OAuthAppFromJson(r.Body)
if oauthApp == nil {
c.SetInvalidParam("oauth_app")
return
}
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) {
c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH)
return
}
oauthApp.CreatorId = c.Session.UserId
rapp, err := app.CreateOAuthApp(oauthApp)
if err != nil {
c.Err = err
return
}
c.LogAudit("client_id=" + rapp.Id)
w.WriteHeader(http.StatusCreated)
w.Write([]byte(rapp.ToJson()))
}
func getOAuthApps(c *Context, w http.ResponseWriter, r *http.Request) {
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) {
c.Err = model.NewAppError("getOAuthApps", "api.command.admin_only.app_error", nil, "", http.StatusForbidden)
return
}
var apps []*model.OAuthApp
var err *model.AppError
if app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) {
apps, err = app.GetOAuthApps(c.Params.Page, c.Params.PerPage)
} else if app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) {
apps, err = app.GetOAuthAppsByCreator(c.Session.UserId, c.Params.Page, c.Params.PerPage)
} else {
c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH)
return
}
if err != nil {
c.Err = err
return
}
w.Write([]byte(model.OAuthAppListToJson(apps)))
}
func getOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireAppId()
if c.Err != nil {
return
}
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) {
c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH)
return
}
oauthApp, err := app.GetOAuthApp(c.Params.AppId)
if err != nil {
c.Err = err
return
}
if oauthApp.CreatorId != c.Session.UserId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH)
return
}
w.Write([]byte(oauthApp.ToJson()))
}
func getOAuthAppInfo(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireAppId()
if c.Err != nil {
return
}
oauthApp, err := app.GetOAuthApp(c.Params.AppId)
if err != nil {
c.Err = err
return
}
oauthApp.Sanitize()
w.Write([]byte(oauthApp.ToJson()))
}
func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireAppId()
if c.Err != nil {
return
}
c.LogAudit("attempt")
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) {
c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH)
return
}
oauthApp, err := app.GetOAuthApp(c.Params.AppId)
if err != nil {
c.Err = err
return
}
if c.Session.UserId != oauthApp.CreatorId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH)
return
}
err = app.DeleteOAuthApp(oauthApp.Id)
if err != nil {
c.Err = err
return
}
c.LogAudit("success")
ReturnStatusOK(w)
}
func regenerateOAuthAppSecret(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireAppId()
if c.Err != nil {
return
}
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) {
c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH)
return
}
oauthApp, err := app.GetOAuthApp(c.Params.AppId)
if err != nil {
c.Err = err
return
}
if oauthApp.CreatorId != c.Session.UserId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH)
return
}
oauthApp, err = app.RegenerateOAuthAppSecret(oauthApp)
if err != nil {
c.Err = err
return
}
c.LogAudit("success")
w.Write([]byte(oauthApp.ToJson()))
}
func getAuthorizedOAuthApps(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId()
if c.Err != nil {
return
}
if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return
}
apps, err := app.GetAuthorizedAppsForUser(c.Params.UserId, c.Params.Page, c.Params.PerPage)
if err != nil {
c.Err = err
return
}
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
}
c.LogAudit("attempt")
redirectUrl, err := app.AllowOAuthAppAccessToUser(c.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 := app.DeauthorizeOAuthAppForUser(c.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 !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
err := model.NewAppError("authorizeOAuth", "api.oauth.authorize_oauth.disabled.app_error", nil, "", http.StatusNotImplemented)
utils.RenderWebError(err, w, r)
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"),
}
if err := authRequest.IsValid(); err != nil {
utils.RenderWebError(err, w, r)
return
}
oauthApp, err := app.GetOAuthApp(authRequest.ClientId)
if err != nil {
utils.RenderWebError(err, w, r)
return
}
// here we should check if the user is logged in
if len(c.Session.UserId) == 0 {
http.Redirect(w, r, c.GetSiteURLHeader()+"/login?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound)
return
}
isAuthorized := false
if _, err := app.GetPreferenceByCategoryAndNameForUser(c.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 {
authRequest.ResponseType = model.AUTHCODE_RESPONSE_TYPE
redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, authRequest)
if err != nil {
utils.RenderWebError(err, w, r)
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")
w.Header().Set("Cache-Control", "no-cache, max-age=31556926, public")
http.ServeFile(w, r, utils.FindDir(model.CLIENT_DIR)+"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 := app.GetOAuthAccessToken(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
code := r.URL.Query().Get("code")
if len(code) == 0 {
c.Err = model.NewAppError("completeOAuth", "api.oauth.complete_oauth.missing_code.app_error", map[string]interface{}{"service": strings.Title(service)}, "URL: "+r.URL.String(), http.StatusBadRequest)
return
}
state := r.URL.Query().Get("state")
uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete"
body, teamId, props, err := app.AuthorizeOAuthUser(service, code, state, uri)
if err != nil {
c.Err = err
return
}
user, err := app.CompleteOAuth(service, body, teamId, props)
if err != nil {
c.Err = err
return
}
action := props["action"]
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 := app.DoLogin(w, r, user, "")
if err != nil {
c.Err = err
return
}
c.Session = *session
redirectUrl = c.GetSiteURLHeader()
}
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 := app.GetTeamIdFromQuery(r.URL.Query())
if err != nil {
c.Err = err
return
}
if authUrl, err := app.GetOAuthLoginEndpoint(c.Params.Service, teamId, redirectTo, loginHint); err != nil {
c.Err = err
return
} else {
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 !utils.Cfg.TeamSettings.EnableUserCreation {
c.Err = model.NewAppError("signupWithOAuth", "api.oauth.singup_with_oauth.disabled.app_error", nil, "", http.StatusNotImplemented)
return
}
teamId, err := app.GetTeamIdFromQuery(r.URL.Query())
if err != nil {
c.Err = err
return
}
if authUrl, err := app.GetOAuthSignupEndpoint(c.Params.Service, teamId); err != nil {
c.Err = err
return
} else {
http.Redirect(w, r, authUrl, http.StatusFound)
}
}

611
api4/oauth_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,611 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
import (
"net/http"
"net/url"
"strconv"
"testing"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
func TestCreateOAuthApp(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
Client := th.Client
AdminClient := th.SystemAdminClient
enableOAuth := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
adminOnly := *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly
}()
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
utils.SetDefaultRolesBasedOnConfig()
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, resp := AdminClient.CreateOAuthApp(oapp)
CheckNoError(t, resp)
CheckCreatedStatus(t, resp)
if rapp.Name != oapp.Name {
t.Fatal("names did not match")
}
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
utils.SetDefaultRolesBasedOnConfig()
_, resp = Client.CreateOAuthApp(oapp)
CheckForbiddenStatus(t, resp)
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
utils.SetDefaultRolesBasedOnConfig()
_, resp = Client.CreateOAuthApp(oapp)
CheckNoError(t, resp)
CheckCreatedStatus(t, resp)
oapp.Name = ""
_, resp = AdminClient.CreateOAuthApp(oapp)
CheckBadRequestStatus(t, resp)
if r, err := Client.DoApiPost("/oauth/apps", "garbage"); err == nil {
t.Fatal("should have failed")
} else {
if r.StatusCode != http.StatusBadRequest {
t.Log("actual: " + strconv.Itoa(r.StatusCode))
t.Log("expected: " + strconv.Itoa(http.StatusBadRequest))
t.Fatal("wrong status code")
}
}
Client.Logout()
_, resp = Client.CreateOAuthApp(oapp)
CheckUnauthorizedStatus(t, resp)
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = false
oapp.Name = GenerateTestAppName()
_, resp = AdminClient.CreateOAuthApp(oapp)
CheckNotImplementedStatus(t, resp)
}
func TestGetOAuthApps(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
Client := th.Client
AdminClient := th.SystemAdminClient
enableOAuth := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
adminOnly := *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly
}()
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
utils.SetDefaultRolesBasedOnConfig()
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, resp := AdminClient.CreateOAuthApp(oapp)
CheckNoError(t, resp)
oapp.Name = GenerateTestAppName()
rapp2, resp := Client.CreateOAuthApp(oapp)
CheckNoError(t, resp)
apps, resp := AdminClient.GetOAuthApps(0, 1000)
CheckNoError(t, resp)
found1 := false
found2 := false
for _, a := range apps {
if a.Id == rapp.Id {
found1 = true
}
if a.Id == rapp2.Id {
found2 = true
}
}
if !found1 || !found2 {
t.Fatal("missing oauth app")
}
apps, resp = AdminClient.GetOAuthApps(1, 1)
CheckNoError(t, resp)
if len(apps) != 1 {
t.Fatal("paging failed")
}
apps, resp = Client.GetOAuthApps(0, 1000)
CheckNoError(t, resp)
if len(apps) != 1 && apps[0].Id != rapp2.Id {
t.Fatal("wrong apps returned")
}
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
utils.SetDefaultRolesBasedOnConfig()
_, resp = Client.GetOAuthApps(0, 1000)
CheckForbiddenStatus(t, resp)
Client.Logout()
_, resp = Client.GetOAuthApps(0, 1000)
CheckUnauthorizedStatus(t, resp)
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = false
_, resp = AdminClient.GetOAuthApps(0, 1000)
CheckNotImplementedStatus(t, resp)
}
func TestGetOAuthApp(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
Client := th.Client
AdminClient := th.SystemAdminClient
enableOAuth := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
adminOnly := *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly
}()
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
utils.SetDefaultRolesBasedOnConfig()
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, resp := AdminClient.CreateOAuthApp(oapp)
CheckNoError(t, resp)
oapp.Name = GenerateTestAppName()
rapp2, resp := Client.CreateOAuthApp(oapp)
CheckNoError(t, resp)
rrapp, resp := AdminClient.GetOAuthApp(rapp.Id)
CheckNoError(t, resp)
if rapp.Id != rrapp.Id {
t.Fatal("wrong app")
}
if rrapp.ClientSecret == "" {
t.Fatal("should not be sanitized")
}
rrapp2, resp := AdminClient.GetOAuthApp(rapp2.Id)
CheckNoError(t, resp)
if rapp2.Id != rrapp2.Id {
t.Fatal("wrong app")
}
if rrapp2.ClientSecret == "" {
t.Fatal("should not be sanitized")
}
_, resp = Client.GetOAuthApp(rapp2.Id)
CheckNoError(t, resp)
_, resp = Client.GetOAuthApp(rapp.Id)
CheckForbiddenStatus(t, resp)
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
utils.SetDefaultRolesBasedOnConfig()
_, resp = Client.GetOAuthApp(rapp2.Id)
CheckForbiddenStatus(t, resp)
Client.Logout()
_, resp = Client.GetOAuthApp(rapp2.Id)
CheckUnauthorizedStatus(t, resp)
_, resp = AdminClient.GetOAuthApp("junk")
CheckBadRequestStatus(t, resp)
_, resp = AdminClient.GetOAuthApp(model.NewId())
CheckNotFoundStatus(t, resp)
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = false
_, resp = AdminClient.GetOAuthApp(rapp.Id)
CheckNotImplementedStatus(t, resp)
}
func TestGetOAuthAppInfo(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
Client := th.Client
AdminClient := th.SystemAdminClient
enableOAuth := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
adminOnly := *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly
}()
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
utils.SetDefaultRolesBasedOnConfig()
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, resp := AdminClient.CreateOAuthApp(oapp)
CheckNoError(t, resp)
oapp.Name = GenerateTestAppName()
rapp2, resp := Client.CreateOAuthApp(oapp)
CheckNoError(t, resp)
rrapp, resp := AdminClient.GetOAuthAppInfo(rapp.Id)
CheckNoError(t, resp)
if rapp.Id != rrapp.Id {
t.Fatal("wrong app")
}
if rrapp.ClientSecret != "" {
t.Fatal("should be sanitized")
}
rrapp2, resp := AdminClient.GetOAuthAppInfo(rapp2.Id)
CheckNoError(t, resp)
if rapp2.Id != rrapp2.Id {
t.Fatal("wrong app")
}
if rrapp2.ClientSecret != "" {
t.Fatal("should be sanitized")
}
_, resp = Client.GetOAuthAppInfo(rapp2.Id)
CheckNoError(t, resp)
_, resp = Client.GetOAuthAppInfo(rapp.Id)
CheckNoError(t, resp)
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = true
utils.SetDefaultRolesBasedOnConfig()
_, resp = Client.GetOAuthAppInfo(rapp2.Id)
CheckNoError(t, resp)
Client.Logout()
_, resp = Client.GetOAuthAppInfo(rapp2.Id)
CheckUnauthorizedStatus(t, resp)
_, resp = AdminClient.GetOAuthAppInfo("junk")
CheckBadRequestStatus(t, resp)
_, resp = AdminClient.GetOAuthAppInfo(model.NewId())
CheckNotFoundStatus(t, resp)
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = false
_, resp = AdminClient.GetOAuthAppInfo(rapp.Id)
CheckNotImplementedStatus(t, resp)
}
func TestDeleteOAuthApp(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
Client := th.Client
AdminClient := th.SystemAdminClient
enableOAuth := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
adminOnly := *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly
}()
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
utils.SetDefaultRolesBasedOnConfig()
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, resp := AdminClient.CreateOAuthApp(oapp)
CheckNoError(t, resp)
oapp.Name = GenerateTestAppName()
rapp2, resp := Client.CreateOAuthApp(oapp)
CheckNoError(t, resp)
pass, resp := AdminClient.DeleteOAuthApp(rapp.Id)
CheckNoError(t, resp)
if !pass {
t.Fatal("should have passed")
}
_, resp = AdminClient.DeleteOAuthApp(rapp2.Id)
CheckNoError(t, resp)
rapp, resp = AdminClient.CreateOAuthApp(oapp)
CheckNoError(t, resp)
oapp.Name = GenerateTestAppName()
rapp2, resp = Client.CreateOAuthApp(oapp)
CheckNoError(t, resp)
_, resp = Client.DeleteOAuthApp(rapp.Id)
CheckForbiddenStatus(t, resp)
_, resp = Client.DeleteOAuthApp(rapp2.Id)
CheckNoError(t, resp)
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
utils.SetDefaultRolesBasedOnConfig()
_, resp = Client.DeleteOAuthApp(rapp.Id)
CheckForbiddenStatus(t, resp)
Client.Logout()
_, resp = Client.DeleteOAuthApp(rapp.Id)
CheckUnauthorizedStatus(t, resp)
_, resp = AdminClient.DeleteOAuthApp("junk")
CheckBadRequestStatus(t, resp)
_, resp = AdminClient.DeleteOAuthApp(model.NewId())
CheckNotFoundStatus(t, resp)
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = false
_, resp = AdminClient.DeleteOAuthApp(rapp.Id)
CheckNotImplementedStatus(t, resp)
}
func TestRegenerateOAuthAppSecret(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
Client := th.Client
AdminClient := th.SystemAdminClient
enableOAuth := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
adminOnly := *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations
defer func() {
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly
}()
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
utils.SetDefaultRolesBasedOnConfig()
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, resp := AdminClient.CreateOAuthApp(oapp)
CheckNoError(t, resp)
oapp.Name = GenerateTestAppName()
rapp2, resp := Client.CreateOAuthApp(oapp)
CheckNoError(t, resp)
rrapp, resp := AdminClient.RegenerateOAuthAppSecret(rapp.Id)
CheckNoError(t, resp)
if rrapp.Id != rapp.Id {
t.Fatal("wrong app")
}
if rrapp.ClientSecret == rapp.ClientSecret {
t.Fatal("secret didn't change")
}
_, resp = AdminClient.RegenerateOAuthAppSecret(rapp2.Id)
CheckNoError(t, resp)
rapp, resp = AdminClient.CreateOAuthApp(oapp)
CheckNoError(t, resp)
oapp.Name = GenerateTestAppName()
rapp2, resp = Client.CreateOAuthApp(oapp)
CheckNoError(t, resp)
_, resp = Client.RegenerateOAuthAppSecret(rapp.Id)
CheckForbiddenStatus(t, resp)
_, resp = Client.RegenerateOAuthAppSecret(rapp2.Id)
CheckNoError(t, resp)
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
utils.SetDefaultRolesBasedOnConfig()
_, resp = Client.RegenerateOAuthAppSecret(rapp.Id)
CheckForbiddenStatus(t, resp)
Client.Logout()
_, resp = Client.RegenerateOAuthAppSecret(rapp.Id)
CheckUnauthorizedStatus(t, resp)
_, resp = AdminClient.RegenerateOAuthAppSecret("junk")
CheckBadRequestStatus(t, resp)
_, resp = AdminClient.RegenerateOAuthAppSecret(model.NewId())
CheckNotFoundStatus(t, resp)
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = false
_, resp = AdminClient.RegenerateOAuthAppSecret(rapp.Id)
CheckNotImplementedStatus(t, resp)
}
func TestGetAuthorizedOAuthAppsForUser(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
Client := th.Client
AdminClient := th.SystemAdminClient
enableOAuth := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
defer func() {
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth
}()
utils.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)
apps, resp := Client.GetAuthorizedOAuthAppsForUser(th.BasicUser.Id, 0, 1000)
CheckNoError(t, resp)
found := false
for _, a := range apps {
if a.Id == rapp.Id {
found = true
}
if a.ClientSecret != "" {
t.Fatal("not sanitized")
}
}
if !found {
t.Fatal("missing app")
}
_, resp = Client.GetAuthorizedOAuthAppsForUser(th.BasicUser2.Id, 0, 1000)
CheckForbiddenStatus(t, resp)
_, resp = Client.GetAuthorizedOAuthAppsForUser("junk", 0, 1000)
CheckBadRequestStatus(t, resp)
Client.Logout()
_, resp = Client.GetAuthorizedOAuthAppsForUser(th.BasicUser.Id, 0, 1000)
CheckUnauthorizedStatus(t, resp)
_, resp = AdminClient.GetAuthorizedOAuthAppsForUser(th.BasicUser.Id, 0, 1000)
CheckNoError(t, resp)
}
func TestAuthorizeOAuthApp(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
Client := th.Client
AdminClient := th.SystemAdminClient
enableOAuth := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
defer func() {
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth
}()
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
utils.SetDefaultRolesBasedOnConfig()
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",
}
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")
}
}
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().InitSystemAdmin()
defer TearDown()
Client := th.Client
AdminClient := th.SystemAdminClient
enableOAuth := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
defer func() {
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth
}()
utils.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)
}

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

@@ -26,12 +26,14 @@ type ApiParams struct {
HookId string HookId string
ReportId string ReportId string
EmojiId string EmojiId string
AppId string
Email string Email string
Username string Username string
TeamName string TeamName string
ChannelName string ChannelName string
PreferenceName string PreferenceName string
Category string Category string
Service string
Page int Page int
PerPage int PerPage int
} }
@@ -77,6 +79,10 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams {
params.EmojiId = val params.EmojiId = val
} }
if val, ok := props["app_id"]; ok {
params.AppId = val
}
if val, ok := props["email"]; ok { if val, ok := props["email"]; ok {
params.Email = val params.Email = val
} }
@@ -97,6 +103,10 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams {
params.Category = val params.Category = val
} }
if val, ok := props["service"]; ok {
params.Category = val
}
if val, ok := props["preference_name"]; ok { if val, ok := props["preference_name"]; ok {
params.PreferenceName = val params.PreferenceName = val
} }

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

@@ -84,50 +84,50 @@ func GetOAuthAppsByCreator(userId string, page, perPage int) ([]*model.OAuthApp,
} }
} }
func AllowOAuthAppAccessToUser(userId, responseType, clientId, redirectUri, scope, state string) (string, *model.AppError) { func AllowOAuthAppAccessToUser(userId string, authRequest *model.AuthorizeRequest) (string, *model.AppError) {
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented)
} }
if len(scope) == 0 { if len(authRequest.Scope) == 0 {
scope = model.DEFAULT_SCOPE authRequest.Scope = model.DEFAULT_SCOPE
} }
var oauthApp *model.OAuthApp var oauthApp *model.OAuthApp
if result := <-Srv.Store.OAuth().GetApp(clientId); result.Err != nil { if result := <-Srv.Store.OAuth().GetApp(authRequest.ClientId); result.Err != nil {
return "", result.Err return "", result.Err
} else { } else {
oauthApp = result.Data.(*model.OAuthApp) oauthApp = result.Data.(*model.OAuthApp)
} }
if !oauthApp.IsValidRedirectURL(redirectUri) { if !oauthApp.IsValidRedirectURL(authRequest.RedirectUri) {
return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest) return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest)
} }
if responseType != model.AUTHCODE_RESPONSE_TYPE { if authRequest.ResponseType != model.AUTHCODE_RESPONSE_TYPE {
return redirectUri + "?error=unsupported_response_type&state=" + state, nil return authRequest.RedirectUri + "?error=unsupported_response_type&state=" + authRequest.State, nil
} }
authData := &model.AuthData{UserId: userId, ClientId: clientId, CreateAt: model.GetMillis(), RedirectUri: redirectUri, State: state, Scope: scope} authData := &model.AuthData{UserId: userId, ClientId: authRequest.ClientId, CreateAt: model.GetMillis(), RedirectUri: authRequest.RedirectUri, State: authRequest.State, Scope: authRequest.Scope}
authData.Code = model.HashPassword(fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, userId)) authData.Code = model.HashPassword(fmt.Sprintf("%v:%v:%v:%v", authRequest.ClientId, authRequest.RedirectUri, authData.CreateAt, userId))
// this saves the OAuth2 app as authorized // this saves the OAuth2 app as authorized
authorizedApp := model.Preference{ authorizedApp := model.Preference{
UserId: userId, UserId: userId,
Category: model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, Category: model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP,
Name: clientId, Name: authRequest.ClientId,
Value: scope, Value: authRequest.Scope,
} }
if result := <-Srv.Store.Preference().Save(&model.Preferences{authorizedApp}); result.Err != nil { if result := <-Srv.Store.Preference().Save(&model.Preferences{authorizedApp}); result.Err != nil {
return redirectUri + "?error=server_error&state=" + state, nil return authRequest.RedirectUri + "?error=server_error&state=" + authRequest.State, nil
} }
if result := <-Srv.Store.OAuth().SaveAuthData(authData); result.Err != nil { if result := <-Srv.Store.OAuth().SaveAuthData(authData); result.Err != nil {
return redirectUri + "?error=server_error&state=" + state, nil return authRequest.RedirectUri + "?error=server_error&state=" + authRequest.State, nil
} }
return redirectUri + "?code=" + url.QueryEscape(authData.Code) + "&state=" + url.QueryEscape(authData.State), nil return authRequest.RedirectUri + "?code=" + url.QueryEscape(authData.Code) + "&state=" + url.QueryEscape(authData.State), nil
} }
func GetOAuthAccessToken(clientId, grantType, redirectUri, code, secret, refreshToken string) (*model.AccessResponse, *model.AppError) { func GetOAuthAccessToken(clientId, grantType, redirectUri, code, secret, refreshToken string) (*model.AccessResponse, *model.AppError) {

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

@@ -3611,6 +3611,10 @@
"id": "model.authorize.is_valid.expires.app_error", "id": "model.authorize.is_valid.expires.app_error",
"translation": "Expires in must be set" "translation": "Expires in must be set"
}, },
{
"id": "model.authorize.is_valid.response_type.app_error",
"translation": "Invalid response type"
},
{ {
"id": "model.authorize.is_valid.redirect_uri.app_error", "id": "model.authorize.is_valid.redirect_uri.app_error",
"translation": "Invalid redirect uri" "translation": "Invalid redirect uri"

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

@@ -6,6 +6,7 @@ package model
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"net/http"
) )
const ( const (
@@ -25,6 +26,14 @@ type AuthData struct {
Scope string `json:"scope"` Scope string `json:"scope"`
} }
type AuthorizeRequest struct {
ResponseType string `json:"response_type"`
ClientId string `json:"client_id"`
RedirectUri string `json:"redirect_uri"`
Scope string `json:"scope"`
State string `json:"state"`
}
// IsValid validates the AuthData and returns an error if it isn't configured // IsValid validates the AuthData and returns an error if it isn't configured
// correctly. // correctly.
func (ad *AuthData) IsValid() *AppError { func (ad *AuthData) IsValid() *AppError {
@@ -64,6 +73,33 @@ func (ad *AuthData) IsValid() *AppError {
return nil return nil
} }
// IsValid validates the AuthorizeRequest and returns an error if it isn't configured
// correctly.
func (ar *AuthorizeRequest) IsValid() *AppError {
if len(ar.ClientId) != 26 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.ResponseType) == 0 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.response_type.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.RedirectUri) == 0 || len(ar.RedirectUri) > 256 || !IsValidHttpUrl(ar.RedirectUri) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
if len(ar.State) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
if len(ar.Scope) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
return nil
}
func (ad *AuthData) PreSave() { func (ad *AuthData) PreSave() {
if ad.ExpiresIn == 0 { if ad.ExpiresIn == 0 {
ad.ExpiresIn = AUTHCODE_EXPIRE_TIME ad.ExpiresIn = AUTHCODE_EXPIRE_TIME
@@ -98,6 +134,26 @@ func AuthDataFromJson(data io.Reader) *AuthData {
} }
} }
func (ar *AuthorizeRequest) ToJson() string {
b, err := json.Marshal(ar)
if err != nil {
return ""
} else {
return string(b)
}
}
func AuthorizeRequestFromJson(data io.Reader) *AuthorizeRequest {
decoder := json.NewDecoder(data)
var ar AuthorizeRequest
err := decoder.Decode(&ar)
if err == nil {
return &ar
} else {
return nil
}
}
func (ad *AuthData) IsExpired() bool { func (ad *AuthData) IsExpired() bool {
if GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) { if GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) {

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

@@ -20,6 +20,17 @@ func TestAuthJson(t *testing.T) {
if a1.Code != ra1.Code { if a1.Code != ra1.Code {
t.Fatal("codes didn't match") t.Fatal("codes didn't match")
} }
a2 := AuthorizeRequest{}
a2.ClientId = NewId()
a2.Scope = NewId()
json = a2.ToJson()
ra2 := AuthorizeRequestFromJson(strings.NewReader(json))
if a2.ClientId != ra2.ClientId {
t.Fatal("client ids didn't match")
}
} }
func TestAuthPreSave(t *testing.T) { func TestAuthPreSave(t *testing.T) {

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

@@ -242,24 +242,32 @@ func (c *Client4) GetReactionsRoute() string {
return fmt.Sprintf("/reactions") return fmt.Sprintf("/reactions")
} }
func (c *Client4) GetOAuthAppsRoute() string {
return fmt.Sprintf("/oauth/apps")
}
func (c *Client4) GetOAuthAppRoute(appId string) string {
return fmt.Sprintf("/oauth/apps/%v", appId)
}
func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) { func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) {
return c.DoApiRequest(http.MethodGet, url, "", etag) return c.DoApiRequest(http.MethodGet, c.ApiUrl+url, "", etag)
} }
func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError) { func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError) {
return c.DoApiRequest(http.MethodPost, url, data, "") return c.DoApiRequest(http.MethodPost, c.ApiUrl+url, data, "")
} }
func (c *Client4) DoApiPut(url string, data string) (*http.Response, *AppError) { func (c *Client4) DoApiPut(url string, data string) (*http.Response, *AppError) {
return c.DoApiRequest(http.MethodPut, url, data, "") return c.DoApiRequest(http.MethodPut, c.ApiUrl+url, data, "")
} }
func (c *Client4) DoApiDelete(url string) (*http.Response, *AppError) { func (c *Client4) DoApiDelete(url string) (*http.Response, *AppError) {
return c.DoApiRequest(http.MethodDelete, url, "", "") return c.DoApiRequest(http.MethodDelete, c.ApiUrl+url, "", "")
} }
func (c *Client4) DoApiRequest(method, url, data, etag string) (*http.Response, *AppError) { func (c *Client4) DoApiRequest(method, url, data, etag string) (*http.Response, *AppError) {
rq, _ := http.NewRequest(method, c.ApiUrl+url, strings.NewReader(data)) rq, _ := http.NewRequest(method, url, strings.NewReader(data))
rq.Close = true rq.Close = true
if len(etag) > 0 { if len(etag) > 0 {
@@ -2211,6 +2219,101 @@ func (c *Client4) GetLogs(page, perPage int) ([]string, *Response) {
} }
} }
// OAuth Section
// CreateOAuthApp will register a new OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider.
func (c *Client4) CreateOAuthApp(app *OAuthApp) (*OAuthApp, *Response) {
if r, err := c.DoApiPost(c.GetOAuthAppsRoute(), app.ToJson()); err != nil {
return nil, &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return OAuthAppFromJson(r.Body), BuildResponse(r)
}
}
// GetOAuthApps gets a page of registered OAuth 2.0 client applications with Mattermost acting as an OAuth 2.0 service provider.
func (c *Client4) GetOAuthApps(page, perPage int) ([]*OAuthApp, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
if r, err := c.DoApiGet(c.GetOAuthAppsRoute()+query, ""); err != nil {
return nil, &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return OAuthAppListFromJson(r.Body), BuildResponse(r)
}
}
// GetOAuthApp gets a registered OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider.
func (c *Client4) GetOAuthApp(appId string) (*OAuthApp, *Response) {
if r, err := c.DoApiGet(c.GetOAuthAppRoute(appId), ""); err != nil {
return nil, &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return OAuthAppFromJson(r.Body), BuildResponse(r)
}
}
// GetOAuthAppInfo gets a sanitized version of a registered OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider.
func (c *Client4) GetOAuthAppInfo(appId string) (*OAuthApp, *Response) {
if r, err := c.DoApiGet(c.GetOAuthAppRoute(appId)+"/info", ""); err != nil {
return nil, &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return OAuthAppFromJson(r.Body), BuildResponse(r)
}
}
// DeleteOAuthApp deletes a registered OAuth 2.0 client application.
func (c *Client4) DeleteOAuthApp(appId string) (bool, *Response) {
if r, err := c.DoApiDelete(c.GetOAuthAppRoute(appId)); err != nil {
return false, &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return CheckStatusOK(r), BuildResponse(r)
}
}
// RegenerateOAuthAppSecret regenerates the client secret for a registered OAuth 2.0 client application.
func (c *Client4) RegenerateOAuthAppSecret(appId string) (*OAuthApp, *Response) {
if r, err := c.DoApiPost(c.GetOAuthAppRoute(appId)+"/regen_secret", ""); err != nil {
return nil, &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return OAuthAppFromJson(r.Body), BuildResponse(r)
}
}
// GetAuthorizedOAuthAppsForUser gets a page of OAuth 2.0 client applications the user has authorized to use access their account.
func (c *Client4) GetAuthorizedOAuthAppsForUser(userId string, page, perPage int) ([]*OAuthApp, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/oauth/apps/authorized"+query, ""); err != nil {
return nil, &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return OAuthAppListFromJson(r.Body), BuildResponse(r)
}
}
// AuthorizeOAuthApp will authorize an OAuth 2.0 client application to access a user's account and provide a redirect link to follow.
func (c *Client4) AuthorizeOAuthApp(authRequest *AuthorizeRequest) (string, *Response) {
if r, err := c.DoApiRequest(http.MethodPost, c.Url+"/oauth/authorize", authRequest.ToJson(), ""); err != nil {
return "", &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return MapFromJson(r.Body)["redirect"], BuildResponse(r)
}
}
// DeauthorizeOAuthApp will deauthorize an OAuth 2.0 client application from accessing a user's account.
func (c *Client4) DeauthorizeOAuthApp(appId string) (bool, *Response) {
requestData := map[string]string{"client_id": appId}
if r, err := c.DoApiRequest(http.MethodPost, c.Url+"/oauth/deauthorize", MapToJson(requestData), ""); err != nil {
return false, &Response{StatusCode: r.StatusCode, Error: err}
} else {
defer closeBody(r)
return CheckStatusOK(r), BuildResponse(r)
}
}
// Commands Section // Commands Section
// CreateCommand will create a new command if the user have the right permissions. // CreateCommand will create a new command if the user have the right permissions.

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

@@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net/http"
"unicode/utf8" "unicode/utf8"
) )
@@ -36,50 +37,50 @@ type OAuthApp struct {
func (a *OAuthApp) IsValid() *AppError { func (a *OAuthApp) IsValid() *AppError {
if len(a.Id) != 26 { if len(a.Id) != 26 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "") return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "", http.StatusBadRequest)
} }
if a.CreateAt == 0 { if a.CreateAt == 0 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if a.UpdateAt == 0 { if a.UpdateAt == 0 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.CreatorId) != 26 { if len(a.CreatorId) != 26 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 { if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.Name) == 0 || len(a.Name) > 64 { if len(a.Name) == 0 || len(a.Name) > 64 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 { if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
for _, callback := range a.CallbackUrls { for _, callback := range a.CallbackUrls {
if !IsValidHttpUrl(callback) { if !IsValidHttpUrl(callback) {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "") return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "", http.StatusBadRequest)
} }
} }
if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) { if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if utf8.RuneCountInString(a.Description) > 512 { if utf8.RuneCountInString(a.Description) > 512 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.IconURL) > 0 { if len(a.IconURL) > 0 {
if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) { if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
} }

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

@@ -5,7 +5,10 @@ package utils
import ( import (
"net/http" "net/http"
"net/url"
"strings" "strings"
"github.com/mattermost/platform/model"
) )
type OriginCheckerProc func(*http.Request) bool type OriginCheckerProc func(*http.Request) bool
@@ -22,3 +25,28 @@ func GetOriginChecker(r *http.Request) OriginCheckerProc {
return nil return nil
} }
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
T, _ := GetTranslationsAndLocale(w, r)
title := T("api.templates.error.title", map[string]interface{}{"SiteName": ClientCfg["SiteName"]})
message := err.Message
details := err.DetailedError
link := "/"
linkMessage := T("api.templates.error.link")
status := http.StatusTemporaryRedirect
if err.StatusCode != http.StatusInternalServerError {
status = err.StatusCode
}
http.Redirect(
w,
r,
"/error?title="+url.QueryEscape(title)+
"&message="+url.QueryEscape(message)+
"&details="+url.QueryEscape(details)+
"&link="+url.QueryEscape(link)+
"&linkmessage="+url.QueryEscape(linkMessage),
status)
}