PLT-3484 OAuth2 Service Provider (#3632)
* PLT-3484 OAuth2 Service Provider * PM text review for OAuth 2.0 Service Provider * PLT-3484 OAuth2 Service Provider UI tweaks (#3668) * Tweaks to help text * Pushing OAuth improvements (#3680) * Re-arrange System Console for OAuth 2.0 Provider
Этот коммит содержится в:
коммит произвёл
Harrison Healey
родитель
ea027c8de4
Коммит
5bc3cea6fe
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Ссылка в новой задаче
Block a user