diff options
Diffstat (limited to 'model')
-rw-r--r-- | model/access.go | 25 | ||||
-rw-r--r-- | model/access_test.go | 10 | ||||
-rw-r--r-- | model/authorize.go | 5 | ||||
-rw-r--r-- | model/client.go | 46 | ||||
-rw-r--r-- | model/oauth.go | 40 | ||||
-rw-r--r-- | model/oauth_test.go | 8 | ||||
-rw-r--r-- | model/preference.go | 3 |
7 files changed, 126 insertions, 11 deletions
diff --git a/model/access.go b/model/access.go index 877b3c4f0..85417fce9 100644 --- a/model/access.go +++ b/model/access.go @@ -15,10 +15,12 @@ const ( ) type AccessData struct { - AuthCode string `json:"auth_code"` + ClientId string `json:"client_id"` + UserId string `json:"user_id"` Token string `json:"token"` RefreshToken string `json:"refresh_token"` RedirectUri string `json:"redirect_uri"` + ExpiresAt int64 `json:"expires_at"` } type AccessResponse struct { @@ -33,8 +35,12 @@ type AccessResponse struct { // correctly. func (ad *AccessData) IsValid() *AppError { - if len(ad.AuthCode) == 0 || len(ad.AuthCode) > 128 { - return NewLocAppError("AccessData.IsValid", "model.access.is_valid.auth_code.app_error", nil, "") + if len(ad.ClientId) == 0 || len(ad.ClientId) > 26 { + return NewLocAppError("AccessData.IsValid", "model.access.is_valid.client_id.app_error", nil, "") + } + + if len(ad.UserId) == 0 || len(ad.UserId) > 26 { + return NewLocAppError("AccessData.IsValid", "model.access.is_valid.user_id.app_error", nil, "") } if len(ad.Token) != 26 { @@ -52,6 +58,19 @@ func (ad *AccessData) IsValid() *AppError { return nil } +func (me *AccessData) IsExpired() bool { + + if me.ExpiresAt <= 0 { + return false + } + + if GetMillis() > me.ExpiresAt { + return true + } + + return false +} + func (ad *AccessData) ToJson() string { b, err := json.Marshal(ad) if err != nil { diff --git a/model/access_test.go b/model/access_test.go index a018a2919..0eca302ba 100644 --- a/model/access_test.go +++ b/model/access_test.go @@ -10,7 +10,8 @@ import ( func TestAccessJson(t *testing.T) { a1 := AccessData{} - a1.AuthCode = NewId() + a1.ClientId = NewId() + a1.UserId = NewId() a1.Token = NewId() a1.RefreshToken = NewId() @@ -29,7 +30,12 @@ func TestAccessIsValid(t *testing.T) { t.Fatal("should have failed") } - ad.AuthCode = NewId() + ad.ClientId = NewId() + if err := ad.IsValid(); err == nil { + t.Fatal("should have failed") + } + + ad.UserId = NewId() if err := ad.IsValid(); err == nil { t.Fatal("should have failed") } diff --git a/model/authorize.go b/model/authorize.go index e0d665bae..2b4017e9c 100644 --- a/model/authorize.go +++ b/model/authorize.go @@ -11,6 +11,7 @@ import ( const ( AUTHCODE_EXPIRE_TIME = 60 * 10 // 10 minutes AUTHCODE_RESPONSE_TYPE = "code" + DEFAULT_SCOPE = "user" ) type AuthData struct { @@ -71,6 +72,10 @@ func (ad *AuthData) PreSave() { if ad.CreateAt == 0 { ad.CreateAt = GetMillis() } + + if len(ad.Scope) == 0 { + ad.Scope = DEFAULT_SCOPE + } } func (ad *AuthData) ToJson() string { diff --git a/model/client.go b/model/client.go index 23648050f..cad551613 100644 --- a/model/client.go +++ b/model/client.go @@ -1446,6 +1446,8 @@ func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) { } } +// RegisterApp creates a new OAuth2 app to be used with the OAuth2 Provider. On success +// it returns the created app. Must be authenticated as a user. func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil { return nil, err @@ -1456,6 +1458,9 @@ func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { } } +// AllowOAuth allows a new session by an OAuth2 App. On success +// it returns the url to be redirected back to the app which initiated the oauth2 flow. +// Must be authenticated as a user. func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (*Result, *AppError) { if r, err := c.DoApiGet("/oauth/allow?response_type="+rspType+"&client_id="+clientId+"&redirect_uri="+url.QueryEscape(redirect)+"&scope="+scope+"&state="+url.QueryEscape(state), "", ""); err != nil { return nil, err @@ -1466,8 +1471,47 @@ func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (* } } +// GetOAuthAppsByUser returns the OAuth2 Apps registered by the user. On success +// it returns a list of OAuth2 Apps from the same user or all the registered apps if the user +// is a System Administrator. Must be authenticated as a user. +func (c *Client) GetOAuthAppsByUser() (*Result, *AppError) { + if r, err := c.DoApiGet("/oauth/list", "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), OAuthAppListFromJson(r.Body)}, nil + } +} + +// GetOAuthAppInfo lookup an OAuth2 App using the client_id. On success +// it returns a Sanitized OAuth2 App. Must be authenticated as a user. +func (c *Client) GetOAuthAppInfo(clientId string) (*Result, *AppError) { + if r, err := c.DoApiGet("/oauth/app/"+clientId, "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil + } +} + +// DeleteOAuthApp deletes an OAuth2 app, the app must be deleted by the same user who created it or +// a System Administrator. On success returs Status OK. Must be authenticated as a user. +func (c *Client) DeleteOAuthApp(id string) (*Result, *AppError) { + data := make(map[string]string) + data["id"] = id + if r, err := c.DoApiPost("/oauth/delete", MapToJson(data)); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil + } +} + func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) { - if r, err := c.DoApiPost("/oauth/access_token", data.Encode()); err != nil { + if r, err := c.DoPost(API_URL_SUFFIX+"/oauth/access_token", data.Encode(), "application/x-www-form-urlencoded"); err != nil { return nil, err } else { defer closeBody(r) diff --git a/model/oauth.go b/model/oauth.go index c54df107c..cfe643c9a 100644 --- a/model/oauth.go +++ b/model/oauth.go @@ -25,8 +25,10 @@ type OAuthApp struct { ClientSecret string `json:"client_secret"` Name string `json:"name"` Description string `json:"description"` + IconURL string `json:"icon_url"` CallbackUrls StringArray `json:"callback_urls"` Homepage string `json:"homepage"` + IsTrusted bool `json:"is_trusted"` } // IsValid validates the app and returns an error if it isn't configured @@ -61,7 +63,13 @@ func (a *OAuthApp) IsValid() *AppError { return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id) } - if len(a.Homepage) == 0 || len(a.Homepage) > 256 { + for _, callback := range a.CallbackUrls { + if !IsValidHttpUrl(callback) { + return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "") + } + } + + 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) } @@ -69,6 +77,12 @@ func (a *OAuthApp) IsValid() *AppError { return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id) } + 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 nil } @@ -85,10 +99,6 @@ func (a *OAuthApp) PreSave() { a.CreateAt = GetMillis() a.UpdateAt = a.CreateAt - - if len(a.ClientSecret) > 0 { - a.ClientSecret = HashPassword(a.ClientSecret) - } } // PreUpdate should be run before updating the app in the db. @@ -157,3 +167,23 @@ func OAuthAppMapFromJson(data io.Reader) map[string]*OAuthApp { return nil } } + +func OAuthAppListToJson(l []*OAuthApp) string { + b, err := json.Marshal(l) + if err != nil { + return "" + } else { + return string(b) + } +} + +func OAuthAppListFromJson(data io.Reader) []*OAuthApp { + decoder := json.NewDecoder(data) + var o []*OAuthApp + err := decoder.Decode(&o) + if err == nil { + return o + } else { + return nil + } +} diff --git a/model/oauth_test.go b/model/oauth_test.go index 2ba36666c..e1f88a993 100644 --- a/model/oauth_test.go +++ b/model/oauth_test.go @@ -14,6 +14,7 @@ func TestOAuthAppJson(t *testing.T) { a1.Name = "TestOAuthApp" + NewId() a1.CallbackUrls = []string{"https://nowhere.com"} a1.Homepage = "https://nowhere.com" + a1.IconURL = "https://nowhere.com/icon_image.png" a1.ClientSecret = NewId() json := a1.ToJson() @@ -30,6 +31,7 @@ func TestOAuthAppPreSave(t *testing.T) { a1.Name = "TestOAuthApp" + NewId() a1.CallbackUrls = []string{"https://nowhere.com"} a1.Homepage = "https://nowhere.com" + a1.IconURL = "https://nowhere.com/icon_image.png" a1.ClientSecret = NewId() a1.PreSave() a1.Etag() @@ -42,6 +44,7 @@ func TestOAuthAppPreUpdate(t *testing.T) { a1.Name = "TestOAuthApp" + NewId() a1.CallbackUrls = []string{"https://nowhere.com"} a1.Homepage = "https://nowhere.com" + a1.IconURL = "https://nowhere.com/icon_image.png" a1.ClientSecret = NewId() a1.PreUpdate() } @@ -92,4 +95,9 @@ func TestOAuthAppIsValid(t *testing.T) { if err := app.IsValid(); err != nil { t.Fatal() } + + app.IconURL = "https://nowhere.com/icon_image.png" + if err := app.IsValid(); err != nil { + t.Fatal() + } } diff --git a/model/preference.go b/model/preference.go index 779c41e50..b74e25d81 100644 --- a/model/preference.go +++ b/model/preference.go @@ -22,6 +22,9 @@ const ( PREFERENCE_CATEGORY_THEME = "theme" // the name for theme props is the team id + PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP = "oauth_app" + // the name for oauth_app is the client_id and value is the current scope + PREFERENCE_CATEGORY_LAST = "last" PREFERENCE_NAME_LAST_CHANNEL = "channel" ) |