[WIP] [MM-55031] OAuth Outgoing Connection App integration (#25379)

* OAuthOutgoingConnection model

* added store

* make generated

* add missing license headers

* fix receiver name

* i18n

* i18n sorting

* update migrations from master

* make migrations-extract

* update retrylayer tests

* replaced sql query with id pagination

* fixed flaky tests

* missing columns

* missing columns on save/update

* typo

* improved tests

* remove enum from mysql colum

* add password credentials to store

* license changes

* OAuthOutgoingConnectionInterface

* Oauth -> OAuth

* make generated

* merge migrations

* renamed migrations

* model change suggestions

* refactor test functionsn

* migration typo

* refactor store table names

* updated sanitize test

* cleanup merge

* refactor symbol

* list endpoint

* oauthoutgoingconnection -> outgoingoauthconnection

* signature change

* i18n update

* granttype typo

* naming

* api list

* uppercase typo

* i18n

* missing license header

* fixed path in comments

* updated openapi definitions

* sanitize connections

* make generated

* test license and no feature flag

* removed t.fatal

* updated testhelper calls

* yaml schema fixes

* switched interface name

* suggested translation

* missing i18n translation

* address comments

* updated i18n
Этот коммит содержится в:
Felipe Martin
2023-12-26 10:46:20 +01:00
коммит произвёл GitHub
родитель 287cbad2d5
Коммит 81a1d725a0
24 изменённых файлов: 1074 добавлений и 25 удалений

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

@@ -471,6 +471,14 @@ func (c *Client4) oAuthAppRoute(appId string) string {
return fmt.Sprintf("/oauth/apps/%v", appId)
}
func (c *Client4) outgoingOAuthConnectionsRoute() string {
return "/oauth/outgoing_connections"
}
func (c *Client4) outgoingOAuthConnectionRoute(id string) string {
return fmt.Sprintf("/oauth/outgoing_connections/%s", id)
}
func (c *Client4) jobsRoute() string {
return "/jobs"
}
@@ -6003,6 +6011,36 @@ func (c *Client4) GetOAuthAccessToken(ctx context.Context, data url.Values) (*Ac
return ar, BuildResponse(rp), nil
}
// OutgoingOAuthConnection section
// GetOutgoingOAuthConnections retrieves the outgoing OAuth connections.
func (c *Client4) GetOutgoingOAuthConnections(ctx context.Context, fromID string, limit int) ([]*OutgoingOAuthConnection, *Response, error) {
r, err := c.DoAPIGet(ctx, c.outgoingOAuthConnectionsRoute(), "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var connections []*OutgoingOAuthConnection
if err := json.NewDecoder(r.Body).Decode(&connections); err != nil {
return nil, nil, NewAppError("GetOutgoingOAuthConnections", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return connections, BuildResponse(r), nil
}
// GetOutgoingOAuthConnection retrieves the outgoing OAuth connection with the given ID.
func (c *Client4) GetOutgoingOAuthConnection(ctx context.Context, id string) (*OutgoingOAuthConnection, *Response, error) {
r, err := c.DoAPIGet(ctx, c.outgoingOAuthConnectionRoute(id), "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var connection *OutgoingOAuthConnection
if err := json.NewDecoder(r.Body).Decode(&connection); err != nil {
return nil, nil, NewAppError("GetOutgoingOAuthConnection", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return connection, BuildResponse(r), nil
}
// Elasticsearch Section
// TestElasticsearch will attempt to connect to the configured Elasticsearch server and return OK if configured.

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

@@ -50,6 +50,8 @@ type FeatureFlags struct {
ConsumePostHook bool
CloudAnnualRenewals bool
OutgoingOAuthConnections bool
}
func (f *FeatureFlags) SetDefaults() {
@@ -69,6 +71,7 @@ func (f *FeatureFlags) SetDefaults() {
f.CloudIPFiltering = false
f.ConsumePostHook = false
f.CloudAnnualRenewals = false
f.OutgoingOAuthConnections = false
}
// ToMap returns the feature flags as a map[string]string

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

@@ -158,6 +158,7 @@ type Features struct {
Cloud *bool `json:"cloud"`
SharedChannels *bool `json:"shared_channels"`
RemoteClusterService *bool `json:"remote_cluster_service"`
OutgoingOAuthConnections *bool `json:"outgoing_oauth_connections"`
// after we enabled more features we'll need to control them with this
FutureFeatures *bool `json:"future_features"`
@@ -191,6 +192,7 @@ func (f *Features) ToMap() map[string]any {
"shared_channels": *f.SharedChannels,
"remote_cluster_service": *f.RemoteClusterService,
"future": *f.FutureFeatures,
"outgoing_oauth_connections": *f.OutgoingOAuthConnections,
}
}
@@ -314,6 +316,10 @@ func (f *Features) SetDefaults() {
if f.RemoteClusterService == nil {
f.RemoteClusterService = NewBool(*f.FutureFeatures)
}
if f.OutgoingOAuthConnections == nil {
f.OutgoingOAuthConnections = NewBool(*f.FutureFeatures)
}
}
func (l *License) IsExpired() bool {

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

@@ -27,8 +27,8 @@ type OutgoingOAuthConnection struct {
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
Name string `json:"name"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
ClientId string `json:"client_id,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
CredentialsUsername *string `json:"credentials_username,omitempty"`
CredentialsPassword *string `json:"credentials_password,omitempty"`
OAuthTokenURL string `json:"oauth_token_url"`
@@ -47,6 +47,14 @@ func (oa *OutgoingOAuthConnection) Auditable() map[string]interface{} {
}
}
// Sanitize removes any sensitive fields from the OutgoingOAuthConnection object.
func (oa *OutgoingOAuthConnection) Sanitize() {
oa.ClientId = ""
oa.ClientSecret = ""
oa.CredentialsUsername = nil
oa.CredentialsPassword = nil
}
// IsValid validates the object and returns an error if it isn't properly configured
func (oa *OutgoingOAuthConnection) IsValid() *AppError {
if !IsValidId(oa.Id) {
@@ -65,19 +73,19 @@ func (oa *OutgoingOAuthConnection) IsValid() *AppError {
return NewAppError("OutgoingOAuthConnection.IsValid", "model.outgoing_oauth_connection.is_valid.creator_id.error", nil, "id="+oa.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(oa.Name) > 64 {
if oa.Name == "" || utf8.RuneCountInString(oa.Name) > 64 {
return NewAppError("OutgoingOAuthConnection.IsValid", "model.outgoing_oauth_connection.is_valid.name.error", nil, "id="+oa.Id, http.StatusBadRequest)
}
if len(oa.ClientId) == 0 || utf8.RuneCountInString(oa.ClientId) > 255 {
if oa.ClientId == "" || utf8.RuneCountInString(oa.ClientId) > 255 {
return NewAppError("OutgoingOAuthConnection.IsValid", "model.outgoing_oauth_connection.is_valid.client_id.error", nil, "id="+oa.Id, http.StatusBadRequest)
}
if len(oa.ClientSecret) == 0 || utf8.RuneCountInString(oa.ClientSecret) > 255 {
if oa.ClientSecret == "" || utf8.RuneCountInString(oa.ClientSecret) > 255 {
return NewAppError("OutgoingOAuthConnection.IsValid", "model.outgoing_oauth_connection.is_valid.client_secret.error", nil, "id="+oa.Id, http.StatusBadRequest)
}
if len(oa.OAuthTokenURL) == 0 || utf8.RuneCountInString(oa.OAuthTokenURL) > 256 {
if oa.OAuthTokenURL == "" || utf8.RuneCountInString(oa.OAuthTokenURL) > 256 {
return NewAppError("OutgoingOAuthConnection.IsValid", "model.outgoing_oauth_connection.is_valid.oauth_token_url.error", nil, "id="+oa.Id, http.StatusBadRequest)
}
@@ -137,13 +145,6 @@ func (oa *OutgoingOAuthConnection) Etag() string {
return Etag(oa.Id, oa.UpdateAt)
}
// Sanitize removes any sensitive fields from the OutgoingOAuthConnection object.
func (oa *OutgoingOAuthConnection) Sanitize() {
oa.ClientSecret = ""
oa.CredentialsUsername = nil
oa.CredentialsPassword = nil
}
// OutgoingOAuthConnectionGetConnectionsFilter is used to filter outgoing connections
type OutgoingOAuthConnectionGetConnectionsFilter struct {
OffsetId string

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

@@ -19,7 +19,7 @@ func newValidOutgoingOAuthConnection() *OutgoingOAuthConnection {
ClientId: NewId(),
ClientSecret: NewId(),
OAuthTokenURL: "https://nowhere.com/oauth/token",
GrantType: "client_credentials",
GrantType: OutgoingOAuthConnectionGrantTypeClientCredentials,
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
Audiences: []string{"https://nowhere.com"},
@@ -52,6 +52,17 @@ func TestOutgoingOAuthConnectionIsValid(t *testing.T) {
require.Error(t, oa.IsValid())
},
},
{
name: "empty name",
item: func() *OutgoingOAuthConnection {
oa := newValidOutgoingOAuthConnection()
oa.Name = ""
return oa
},
assert: func(t *testing.T, oa *OutgoingOAuthConnection) {
require.Error(t, oa.IsValid())
},
},
{
name: "invalid create_at",
item: func() *OutgoingOAuthConnection {
@@ -307,6 +318,7 @@ func TestOutgoingOAuthConnectionSanitize(t *testing.T) {
oa := newValidOutgoingOAuthConnection()
oa.Sanitize()
require.Empty(t, oa.ClientId)
require.Empty(t, oa.ClientSecret)
require.Empty(t, oa.CredentialsUsername)
require.Empty(t, oa.CredentialsPassword)