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()
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)
}
}

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

@@ -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