summaryrefslogtreecommitdiffstats
path: root/model
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-04-20 09:55:02 -0400
committerGitHub <noreply@github.com>2017-04-20 09:55:02 -0400
commitbe9624e2adce7c95039e62fc4ee22538d7fa2d2f (patch)
tree318179b4d3a4cb5114f887797a5a4c836e5255d7 /model
parent1a0f8d1b3c7451eac43bfdc5971de060caabf441 (diff)
downloadchat-be9624e2adce7c95039e62fc4ee22538d7fa2d2f.tar.gz
chat-be9624e2adce7c95039e62fc4ee22538d7fa2d2f.tar.bz2
chat-be9624e2adce7c95039e62fc4ee22538d7fa2d2f.zip
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
Diffstat (limited to 'model')
-rw-r--r--model/authorize.go56
-rw-r--r--model/authorize_test.go11
-rw-r--r--model/client4.go113
-rw-r--r--model/oauth.go23
4 files changed, 187 insertions, 16 deletions
diff --git a/model/authorize.go b/model/authorize.go
index 2f290fab2..460b70823 100644
--- a/model/authorize.go
+++ b/model/authorize.go
@@ -6,6 +6,7 @@ package model
import (
"encoding/json"
"io"
+ "net/http"
)
const (
@@ -25,6 +26,14 @@ type AuthData struct {
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
// correctly.
func (ad *AuthData) IsValid() *AppError {
@@ -64,6 +73,33 @@ func (ad *AuthData) IsValid() *AppError {
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() {
if ad.ExpiresIn == 0 {
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 {
if GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) {
diff --git a/model/authorize_test.go b/model/authorize_test.go
index cbb57d54c..3f43a4fc3 100644
--- a/model/authorize_test.go
+++ b/model/authorize_test.go
@@ -20,6 +20,17 @@ func TestAuthJson(t *testing.T) {
if a1.Code != ra1.Code {
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) {
diff --git a/model/client4.go b/model/client4.go
index a7a3607e6..6f8b43c39 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -242,24 +242,32 @@ func (c *Client4) GetReactionsRoute() string {
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) {
- 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) {
- 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) {
- 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) {
- 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) {
- rq, _ := http.NewRequest(method, c.ApiUrl+url, strings.NewReader(data))
+ rq, _ := http.NewRequest(method, url, strings.NewReader(data))
rq.Close = true
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
// CreateCommand will create a new command if the user have the right permissions.
diff --git a/model/oauth.go b/model/oauth.go
index a8aca0ca0..6a3561ed9 100644
--- a/model/oauth.go
+++ b/model/oauth.go
@@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "net/http"
"unicode/utf8"
)
@@ -36,50 +37,50 @@ type OAuthApp struct {
func (a *OAuthApp) IsValid() *AppError {
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 {
- 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 {
- 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 {
- 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 {
- 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 {
- 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 {
- 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 {
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) {
- 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 {
- 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) > 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)
}
}