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
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
1a0f8d1b3c
Коммит
be9624e2ad
@@ -75,6 +75,7 @@ func Setup() *TestHelper {
|
||||
InitRouter()
|
||||
wsapi.InitRouter()
|
||||
app.StartServer()
|
||||
api4.InitApi(false)
|
||||
InitApi()
|
||||
wsapi.InitApi()
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
@@ -242,7 +242,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if c.Err.StatusCode == http.StatusUnauthorized {
|
||||
http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
|
||||
} 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
|
||||
}
|
||||
|
||||
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) {
|
||||
err := model.NewLocAppError("Handle404", "api.context.404.app_error", nil, "")
|
||||
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?"
|
||||
w.Write([]byte(err.ToJson()))
|
||||
} else {
|
||||
RenderWebError(err, w, r)
|
||||
utils.RenderWebError(err, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
182
api/oauth.go
182
api/oauth.go
@@ -5,8 +5,6 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/gorilla/mux"
|
||||
@@ -26,17 +24,8 @@ func InitOAuth() {
|
||||
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]+}/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]+}/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) {
|
||||
@@ -126,7 +115,15 @@ func allowOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
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 {
|
||||
c.Err = err
|
||||
@@ -148,167 +145,6 @@ func getAuthorizedApps(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
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) {
|
||||
params := mux.Vars(r)
|
||||
service := params["service"]
|
||||
|
||||
@@ -28,7 +28,6 @@ func TestOAuthRegisterApp(t *testing.T) {
|
||||
if _, err := Client.RegisterApp(oauthApp); err == nil {
|
||||
t.Fatal("should have failed - oauth providing turned off")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
|
||||
|
||||
Ссылка в новой задаче
Block a user