[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 удалений

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

@@ -52,6 +52,7 @@ build-v4: node_modules playbooks
@cat $(V4_SRC)/ip_filters.yaml >> $(V4_YAML)
@cat $(V4_SRC)/reports.yaml >> $(V4_YAML)
@cat $(V4_SRC)/limits.yaml >> $(V4_YAML)
@cat $(V4_SRC)/outgoing_oauth_connections.yaml >> $(V4_YAML)
@if [ -r $(PLAYBOOKS_SRC)/paths.yaml ]; then cat $(PLAYBOOKS_SRC)/paths.yaml >> $(V4_YAML); fi
@if [ -r $(PLAYBOOKS_SRC)/merged-definitions.yaml ]; then cat $(PLAYBOOKS_SRC)/merged-definitions.yaml >> $(V4_YAML); else cat $(V4_SRC)/definitions.yaml >> $(V4_YAML); fi
@echo Extracting code samples

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

@@ -3580,6 +3580,30 @@ components:
description: The number of active users in the server
type: integer
format: int64
# Outgoing OAuth Connections
OutgoingOAuthConnectionGetItem:
type: object
properties:
id:
description: The unique identifier for the outgoing OAuth connection.
type: string
name:
description: The name of the outgoing OAuth connection.
type: string
create_at:
description: The time in milliseconds the outgoing OAuth connection was created.
type: integer
format: int64
update_at:
description: The time in milliseconds the outgoing OAuth connection was last updated.
type: integer
format: int64
grant_type:
description: The grant type of the outgoing OAuth connection.
type: string
audiences:
description: The audiences of the outgoing OAuth connection.
type: string
externalDocs:
description: Find out more about Mattermost
url: 'https://about.mattermost.com'

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

@@ -0,0 +1,52 @@
/api/v4/oauth/outgoing_connections:
get:
tags:
- oauth
- outgoing_connections
- outgoing_oauth_connections
summary: List all connections
description: >
List all outgoing OAuth connections.
__Minimum server version__: 9.5
operationId: ListOutgoingOAuthConnections
responses:
"200":
description: Successfully fetched outgoing OAuth connections
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/OutgoingOAuthConnectionGetItem"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalServerError"
"501":
$ref: "#/components/responses/NotImplemented"
/api/v4/oauth/outgoing_connections/{connection_id}:
get:
tags:
- oauth
- outgoing_connections
- outgoing_oauth_connections
summary: Get a connection
description: >
Retrieve an outgoing OAuth connection.
__Minimum server version__: 9.5
operationId: GetOutgoingOAuthConnection
responses:
"200":
description: Successfully fetched outgoing OAuth connection
content:
application/json:
schema:
$ref: "#/components/schemas/OutgoingOAuthConnectionGetItem"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalServerError"
"501":
$ref: "#/components/responses/NotImplemented"

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

@@ -143,6 +143,9 @@ type Routes struct {
Reports *mux.Router // 'api/v4/reports'
Limits *mux.Router // 'api/v4/limits'
OutgoingOAuthConnections *mux.Router // 'api/v4/oauth/outgoing_connections'
OutgoingOAuthConnection *mux.Router // 'api/v4/oauth/outgoing_connections/{outgoing_oauth_connection_id:[A-Za-z0-9]+}'
}
type API struct {
@@ -273,6 +276,9 @@ func Init(srv *app.Server) (*API, error) {
api.BaseRoutes.Limits = api.BaseRoutes.APIRoot.PathPrefix("/limits").Subrouter()
api.BaseRoutes.OutgoingOAuthConnections = api.BaseRoutes.APIRoot.PathPrefix("/oauth/outgoing_connections").Subrouter()
api.BaseRoutes.OutgoingOAuthConnection = api.BaseRoutes.APIRoot.PathPrefix("/oauth/outgoing_connections/{outgoing_oauth_connection_id:[A-Za-z0-9]+}").Subrouter()
api.InitUser()
api.InitBot()
api.InitTeam()
@@ -319,6 +325,7 @@ func Init(srv *app.Server) (*API, error) {
api.InitIPFiltering()
api.InitReports()
api.InitLimits()
api.InitOutgoingOAuthConnection()
srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))

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

@@ -0,0 +1,140 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/einterfaces"
)
const (
whereOutgoingOAuthConnection = "outgoingOAuthConnections"
)
func (api *API) InitOutgoingOAuthConnection() {
api.BaseRoutes.OutgoingOAuthConnections.Handle("", api.APISessionRequired(listOutgoingOAuthConnections)).Methods("GET")
api.BaseRoutes.OutgoingOAuthConnection.Handle("", api.APISessionRequired(getOutgoingOAuthConnection)).Methods("GET")
}
func ensureOutgoingOAuthConnectionInterface(c *Context, where string) (einterfaces.OutgoingOAuthConnectionInterface, bool) {
if !c.App.Config().FeatureFlags.OutgoingOAuthConnections {
c.Err = model.NewAppError(where, "api.context.outgoing_oauth_connection.not_available.feature_flag", nil, "", http.StatusNotImplemented)
return nil, false
}
if c.App.OutgoingOAuthConnections() == nil || c.App.License() == nil || c.App.License().SkuShortName != model.LicenseShortSkuEnterprise {
c.Err = model.NewAppError(where, "api.license.upgrade_needed.app_error", nil, "", http.StatusNotImplemented)
return nil, false
}
return c.App.OutgoingOAuthConnections(), true
}
type listOutgoingOAuthConnectionsQuery struct {
FromID string
Limit int
}
// SetDefaults sets the default values for the query.
func (q *listOutgoingOAuthConnectionsQuery) SetDefaults() {
// Set default values
if q.Limit == 0 {
q.Limit = 10
}
}
// IsValid validates the query.
func (q *listOutgoingOAuthConnectionsQuery) IsValid() error {
if q.Limit < 1 || q.Limit > 100 {
return fmt.Errorf("limit must be between 1 and 100")
}
return nil
}
// ToFilter converts the query to a filter that can be used to query the database.
func (q *listOutgoingOAuthConnectionsQuery) ToFilter() model.OutgoingOAuthConnectionGetConnectionsFilter {
return model.OutgoingOAuthConnectionGetConnectionsFilter{
OffsetId: q.FromID,
Limit: q.Limit,
}
}
func NewListOutgoingOAuthConnectionsQueryFromURLQuery(values url.Values) (*listOutgoingOAuthConnectionsQuery, error) {
query := &listOutgoingOAuthConnectionsQuery{}
query.SetDefaults()
fromID := values.Get("from_id")
if fromID != "" {
query.FromID = fromID
}
limit := values.Get("limit")
if limit != "" {
limitInt, err := strconv.Atoi(limit)
if err == nil {
return nil, err
}
query.Limit = limitInt
}
return query, nil
}
func listOutgoingOAuthConnections(c *Context, w http.ResponseWriter, r *http.Request) {
service, ok := ensureOutgoingOAuthConnectionInterface(c, whereOutgoingOAuthConnection)
if !ok {
return
}
query, err := NewListOutgoingOAuthConnectionsQueryFromURLQuery(r.URL.Query())
if err != nil {
c.Err = model.NewAppError(whereOutgoingOAuthConnection, "api.context.outgoing_oauth_connection.list_connections.input_error", nil, err.Error(), http.StatusBadRequest)
return
}
if errValid := query.IsValid(); errValid != nil {
c.Err = model.NewAppError(whereOutgoingOAuthConnection, "api.context.outgoing_oauth_connection.list_connections.input_error", nil, errValid.Error(), http.StatusBadRequest)
return
}
connections, errList := service.GetConnections(c.AppContext, query.ToFilter())
if errList != nil {
c.Err = model.NewAppError(whereOutgoingOAuthConnection, "api.context.outgoing_oauth_connection.list_connections.app_error", nil, errList.Error(), http.StatusInternalServerError)
return
}
service.SanitizeConnections(connections)
if errJSON := json.NewEncoder(w).Encode(connections); errJSON != nil {
c.Err = model.NewAppError(whereOutgoingOAuthConnection, "api.context.outgoing_oauth_connection.list_connections.app_error", nil, errJSON.Error(), http.StatusInternalServerError)
return
}
}
func getOutgoingOAuthConnection(c *Context, w http.ResponseWriter, r *http.Request) {
service, ok := ensureOutgoingOAuthConnectionInterface(c, whereOutgoingOAuthConnection)
if !ok {
return
}
c.RequireOutgoingOAuthConnectionId()
connection, err := service.GetConnection(c.AppContext, c.Params.OutgoingOAuthConnectionID)
if err != nil {
c.Err = model.NewAppError(whereOutgoingOAuthConnection, "api.context.outgoing_oauth_connection.list_connections.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
service.SanitizeConnection(connection)
if err := json.NewEncoder(w).Encode(connection); err != nil {
c.Err = model.NewAppError(whereOutgoingOAuthConnection, "api.context.outgoing_oauth_connection.list_connections.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
}

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

@@ -0,0 +1,337 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
"github.com/mattermost/mattermost/server/v8/channels/web"
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
"github.com/stretchr/testify/require"
)
func newOutgoingOAuthConnection() *model.OutgoingOAuthConnection {
return &model.OutgoingOAuthConnection{
Name: "test",
CreatorId: model.NewId(),
ClientId: "test",
ClientSecret: "test",
OAuthTokenURL: "http://localhost:9999/oauth/token",
GrantType: model.OutgoingOAuthConnectionGrantTypeClientCredentials,
Audiences: []string{"http://example.com"},
}
}
func outgoingOauthConnectionsCleanup(t *testing.T, th *TestHelper) {
t.Helper()
// Remove all connections
conns, errCleanup := th.App.Srv().Store().OutgoingOAuthConnection().GetConnections(th.Context, model.OutgoingOAuthConnectionGetConnectionsFilter{})
require.NoError(t, errCleanup)
for _, c := range conns {
require.NoError(t, th.App.Srv().Store().OutgoingOAuthConnection().DeleteConnection(th.Context, c.Id))
}
}
// Client tests
func TestOutgoingOAuthConnectionGet(t *testing.T) {
t.Run("No license returns 501", func(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTION", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTION")
th := Setup(t).InitBasic()
defer th.TearDown()
outgoingOauthIface := &mocks.OutgoingOAuthConnectionInterface{}
outgoingOauthImpl := th.App.Srv().OutgoingOAuthConnection
defer func() {
th.App.Srv().OutgoingOAuthConnection = outgoingOauthImpl
}()
th.App.Srv().OutgoingOAuthConnection = outgoingOauthIface
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
connections, response, err := th.Client.GetOutgoingOAuthConnections(context.Background(), "", 10)
require.Error(t, err)
require.Nil(t, connections)
require.Equal(t, 501, response.StatusCode)
})
t.Run("license but no feature flag returns 501", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
outgoingOauthIface := &mocks.OutgoingOAuthConnectionInterface{}
outgoingOauthImpl := th.App.Srv().OutgoingOAuthConnection
defer func() {
th.App.Srv().OutgoingOAuthConnection = outgoingOauthImpl
}()
th.App.Srv().OutgoingOAuthConnection = outgoingOauthIface
license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise, "outgoing_oauth_connections")
license.Id = "test-license-id"
th.App.Srv().SetLicense(license)
th.App.Srv().RemoveLicense()
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
connections, response, err := th.Client.GetOutgoingOAuthConnections(context.Background(), "", 10)
require.Error(t, err)
require.Nil(t, connections)
require.Equal(t, 501, response.StatusCode)
})
}
func TestListOutgoingOAutConnection(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS")
th := Setup(t).InitBasic()
defer th.TearDown()
license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise, "outgoing_oauth_connections")
license.Id = "test-license-id"
th.App.Srv().SetLicense(license)
t.Run("empty", func(t *testing.T) {
defer outgoingOauthConnectionsCleanup(t, th)
outgoingOauthIface := &mocks.OutgoingOAuthConnectionInterface{}
th.App.Srv().OutgoingOAuthConnection = outgoingOauthIface
outgoingOauthIface.Mock.On("GetConnections", mock.Anything, mock.Anything).Return([]*model.OutgoingOAuthConnection{}, nil)
outgoingOauthIface.Mock.On("SanitizeConnections", mock.Anything)
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
connections, response, err := th.Client.GetOutgoingOAuthConnections(context.Background(), "", 10)
require.NoError(t, err)
require.Equal(t, 200, response.StatusCode)
require.Equal(t, 0, len(connections))
})
t.Run("return result", func(t *testing.T) {
defer outgoingOauthConnectionsCleanup(t, th)
conn := newOutgoingOAuthConnection()
conn, err := th.App.Srv().Store().OutgoingOAuthConnection().SaveConnection(th.Context, conn)
require.NoError(t, err)
outgoingOauthIface := &mocks.OutgoingOAuthConnectionInterface{}
th.App.Srv().OutgoingOAuthConnection = outgoingOauthIface
outgoingOauthIface.Mock.On("GetConnections", mock.Anything, mock.Anything).Return([]*model.OutgoingOAuthConnection{conn}, nil)
outgoingOauthIface.Mock.On("SanitizeConnections", mock.Anything)
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
connections, response, err := th.Client.GetOutgoingOAuthConnections(context.Background(), "", 10)
require.NoError(t, err)
require.Equal(t, 200, response.StatusCode)
require.Equal(t, 1, len(connections))
require.Equal(t, conn, connections[0])
})
}
func TestGetOutgoingOauthConnection(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS")
th := Setup(t).InitBasic()
defer th.TearDown()
defer th.App.Srv().RemoveLicense()
license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise, "outgoing_oauth_connections")
license.Id = "test-license-id"
th.App.Srv().SetLicense(license)
t.Run("return result", func(t *testing.T) {
defer outgoingOauthConnectionsCleanup(t, th)
conn := newOutgoingOAuthConnection()
conn, err := th.App.Srv().Store().OutgoingOAuthConnection().SaveConnection(th.Context, conn)
require.NoError(t, err)
outgoingOauthIface := &mocks.OutgoingOAuthConnectionInterface{}
outgoingOauthIface.Mock.On("GetConnection", mock.Anything, mock.Anything).Return(conn, nil)
outgoingOauthIface.Mock.On("SanitizeConnection", mock.Anything)
outgoingOauthImpl := th.App.Srv().OutgoingOAuthConnection
defer func() {
th.App.Srv().OutgoingOAuthConnection = outgoingOauthImpl
}()
th.App.Srv().OutgoingOAuthConnection = outgoingOauthIface
th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
connection, response, err := th.Client.GetOutgoingOAuthConnection(context.Background(), conn.Id)
require.NoError(t, err)
require.Equal(t, 200, response.StatusCode)
require.NotNil(t, connection)
require.Equal(t, conn.Id, connection.Id)
require.Equal(t, conn, connection)
})
}
// API tests
func TestEnsureOutgoingOAuthConnectionInterface(t *testing.T) {
t.Run("no feature flag, no interface, no license", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
c := &Context{}
c.AppContext = th.Context
c.App = th.App
c.Logger = th.App.Srv().Log()
th.App.Srv().OutgoingOAuthConnection = nil
_, valid := ensureOutgoingOAuthConnectionInterface(c, "api")
require.False(t, valid)
})
t.Run("feature flag, no interface, no license", func(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS")
th := Setup(t).InitBasic()
defer th.TearDown()
c := &Context{}
c.AppContext = th.Context
c.App = th.App
c.Logger = th.App.Srv().Log()
th.App.Srv().OutgoingOAuthConnection = nil
_, valid := ensureOutgoingOAuthConnectionInterface(c, "api")
require.False(t, valid)
})
t.Run("feature flag, interface defined, no license", func(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS")
th := Setup(t).InitBasic()
defer th.TearDown()
c := &Context{}
c.AppContext = th.Context
c.App = th.App
c.Logger = th.App.Srv().Log()
th.App.Srv().OutgoingOAuthConnection = &mocks.OutgoingOAuthConnectionInterface{}
_, valid := ensureOutgoingOAuthConnectionInterface(c, "api")
require.False(t, valid)
})
t.Run("feature flag, interface defined, valid license", func(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS")
th := Setup(t).InitBasic()
defer th.TearDown()
license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise, "outgoing_oauth_connections")
license.Id = "test-license-id"
th.App.Srv().SetLicense(license)
defer th.App.Srv().RemoveLicense()
th.App.Srv().OutgoingOAuthConnection = &mocks.OutgoingOAuthConnectionInterface{}
c := &Context{}
c.AppContext = th.Context
c.App = th.App
c.Logger = th.App.Srv().Log()
svc, valid := ensureOutgoingOAuthConnectionInterface(c, "api")
require.True(t, valid)
require.NotNil(t, svc)
})
}
func TestOutgoingOAuthConnectionAPIHandlers(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_OUTGOINGOAUTHCONNECTIONS")
th := Setup(t).InitBasic()
defer th.TearDown()
license := model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise, "outgoing_oauth_connections")
license.Id = "test-license-id"
th.App.Srv().SetLicense(license)
defer th.App.Srv().RemoveLicense()
c := &Context{}
c.AppContext = th.Context
c.App = th.App
c.Logger = th.App.Srv().Log()
conn := newOutgoingOAuthConnection()
t.Run("getOutgoingOAuthConnection", func(t *testing.T) {
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Error(err)
}
c.Params = &web.Params{
OutgoingOAuthConnectionID: conn.Id,
}
outgoingOauthIface := &mocks.OutgoingOAuthConnectionInterface{}
th.App.Srv().OutgoingOAuthConnection = outgoingOauthIface
outgoingOauthIface.Mock.On("GetConnection", th.Context, c.Params.OutgoingOAuthConnectionID).Return(conn, nil)
outgoingOauthIface.Mock.On("SanitizeConnection", mock.Anything)
httpRecorder := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
getOutgoingOAuthConnection(c, w, r)
})
handler.ServeHTTP(httpRecorder, req)
require.Equal(t, http.StatusOK, httpRecorder.Code)
require.NotEmpty(t, httpRecorder.Body.String())
var buf bytes.Buffer
require.NoError(t, json.NewEncoder(&buf).Encode(conn))
})
t.Run("listOutgoingOAuthConnections", func(t *testing.T) {
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Error(err)
}
conns := []*model.OutgoingOAuthConnection{conn}
outgoingOauthIface := &mocks.OutgoingOAuthConnectionInterface{}
th.App.Srv().OutgoingOAuthConnection = outgoingOauthIface
outgoingOauthIface.Mock.On("GetConnections", th.Context, mock.Anything).Return(conns, nil)
outgoingOauthIface.Mock.On("SanitizeConnections", mock.Anything)
httpRecorder := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
listOutgoingOAuthConnections(c, w, r)
})
handler.ServeHTTP(httpRecorder, req)
require.Equal(t, http.StatusOK, httpRecorder.Code)
require.NotEmpty(t, httpRecorder.Body.String())
var buf bytes.Buffer
require.NoError(t, json.NewEncoder(&buf).Encode(conn))
})
}

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

@@ -103,11 +103,12 @@ func (a *App) Saml() einterfaces.SamlInterface {
func (a *App) Cloud() einterfaces.CloudInterface {
return a.ch.srv.Cloud
}
func (a *App) IPFiltering() einterfaces.IPFilteringInterface {
return a.ch.srv.IPFiltering
}
func (a *App) OutgoingOAuthConnections() einterfaces.OutgoingOAuthConnectionInterface {
return a.ch.srv.OutgoingOAuthConnection
}
func (a *App) HTTPService() httpservice.HTTPService {
return a.ch.srv.httpService
}

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

@@ -946,6 +946,7 @@ type AppIface interface {
NotifySharedChannelUserUpdate(user *model.User)
OpenInteractiveDialog(request model.OpenDialogRequest) *model.AppError
OriginChecker() func(*http.Request) bool
OutgoingOAuthConnections() einterfaces.OutgoingOAuthConnectionInterface
PatchChannel(c request.CTX, channel *model.Channel, patch *model.ChannelPatch, userID string) (*model.Channel, *model.AppError)
PatchPost(c request.CTX, postID string, patch *model.PostPatch) (*model.Post, *model.AppError)
PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError)

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

@@ -60,13 +60,14 @@ type Channels struct {
// previously fetched notices
cachedNotices model.ProductNotices
AccountMigration einterfaces.AccountMigrationInterface
Compliance einterfaces.ComplianceInterface
DataRetention einterfaces.DataRetentionInterface
MessageExport einterfaces.MessageExportInterface
Saml einterfaces.SamlInterface
Notification einterfaces.NotificationInterface
Ldap einterfaces.LdapInterface
AccountMigration einterfaces.AccountMigrationInterface
Compliance einterfaces.ComplianceInterface
DataRetention einterfaces.DataRetentionInterface
MessageExport einterfaces.MessageExportInterface
Saml einterfaces.SamlInterface
Notification einterfaces.NotificationInterface
OutgoingOAuthConnection einterfaces.OutgoingOAuthConnectionInterface
Ldap einterfaces.LdapInterface
// These are used to prevent concurrent upload requests
// for a given upload session which could cause inconsistencies
@@ -176,6 +177,9 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) {
if notificationInterface != nil {
ch.Notification = notificationInterface(New(ServerConnector(ch)))
}
if outgoingOauthConnectionInterface != nil {
ch.OutgoingOAuthConnection = outgoingOauthConnectionInterface(New(ServerConnector(ch)))
}
if samlInterfaceNew != nil {
ch.Saml = samlInterfaceNew(New(ServerConnector(ch)))
if err := ch.Saml.ConfigureSP(request.EmptyContext(s.Log())); err != nil {

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

@@ -92,6 +92,12 @@ func RegisterNotificationInterface(f func(*App) einterfaces.NotificationInterfac
notificationInterface = f
}
var outgoingOauthConnectionInterface func(*App) einterfaces.OutgoingOAuthConnectionInterface
func RegisterOutgoingOAuthConnectionInterface(f func(*App) einterfaces.OutgoingOAuthConnectionInterface) {
outgoingOauthConnectionInterface = f
}
var ipFilteringInterface func(*App) einterfaces.IPFilteringInterface
func RegisterIPFilteringInterface(f func(*App) einterfaces.IPFilteringInterface) {

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

@@ -12877,6 +12877,23 @@ func (a *OpenTracingAppLayer) OriginChecker() func(*http.Request) bool {
return resultVar0
}
func (a *OpenTracingAppLayer) OutgoingOAuthConnections() einterfaces.OutgoingOAuthConnectionInterface {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.OutgoingOAuthConnections")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0 := a.app.OutgoingOAuthConnections()
return resultVar0
}
func (a *OpenTracingAppLayer) OverrideIconURLIfEmoji(c request.CTX, post *model.Post) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.OverrideIconURLIfEmoji")

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

@@ -143,8 +143,9 @@ type Server struct {
// startSearchEngine bool
skipPostInit bool
Cloud einterfaces.CloudInterface
IPFiltering einterfaces.IPFilteringInterface
Cloud einterfaces.CloudInterface
IPFiltering einterfaces.IPFilteringInterface
OutgoingOAuthConnection einterfaces.OutgoingOAuthConnectionInterface
tracer *tracing.Tracer

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

@@ -196,6 +196,7 @@ func GetClientLicense(l *model.License) map[string]string {
props["Cloud"] = strconv.FormatBool(*l.Features.Cloud)
props["SharedChannels"] = strconv.FormatBool(*l.Features.SharedChannels)
props["RemoteClusterService"] = strconv.FormatBool(*l.Features.RemoteClusterService)
props["OutgoingOAuthConnections"] = strconv.FormatBool(*l.Features.OutgoingOAuthConnections)
props["IsTrial"] = strconv.FormatBool(l.IsTrial)
props["IsGovSku"] = strconv.FormatBool(l.IsGovSku)
}

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

@@ -453,6 +453,17 @@ func (c *Context) RequireAppId() *Context {
return c
}
func (c *Context) RequireOutgoingOAuthConnectionId() *Context {
if c.Err != nil {
return c
}
if !model.IsValidId(c.Params.OutgoingOAuthConnectionID) {
c.SetInvalidURLParam("outgoing_oauth_connection_id")
}
return c
}
func (c *Context) RequireFileId() *Context {
if c.Err != nil {
return c

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

@@ -93,6 +93,7 @@ type Params struct {
GroupSource model.GroupSource
FilterHasMember string
IncludeChannelMemberCount string
OutgoingOAuthConnectionID string
// Cloud
InvoiceId string
@@ -145,6 +146,7 @@ func ParamsFromRequest(r *http.Request) *Params {
params.GroupId = props["group_id"]
params.RemoteId = props["remote_id"]
params.InvoiceId = props["invoice_id"]
params.OutgoingOAuthConnectionID = props["outgoing_oauth_connection_id"]
params.Scope = query.Get("scope")
if val, err := strconv.Atoi(query.Get("page")); err != nil || val < 0 {

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

@@ -0,0 +1,159 @@
// Code generated by mockery v2.23.2. DO NOT EDIT.
// Regenerate this file using `make einterfaces-mocks`.
package mocks
import (
model "github.com/mattermost/mattermost/server/public/model"
request "github.com/mattermost/mattermost/server/public/shared/request"
mock "github.com/stretchr/testify/mock"
)
// OAuthOutgoingConnectionInterface is an autogenerated mock type for the OAuthOutgoingConnectionInterface type
type OAuthOutgoingConnectionInterface struct {
mock.Mock
}
// DeleteConnection provides a mock function with given fields: rctx, id
func (_m *OAuthOutgoingConnectionInterface) DeleteConnection(rctx request.CTX, id string) *model.AppError {
ret := _m.Called(rctx, id)
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.AppError); ok {
r0 = rf(rctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0
}
// GetConnection provides a mock function with given fields: rctx, id
func (_m *OAuthOutgoingConnectionInterface) GetConnection(rctx request.CTX, id string) (*model.OutgoingOAuthConnectionGrantType, *model.AppError) {
ret := _m.Called(rctx, id)
var r0 *model.OutgoingOAuthConnectionGrantType
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, string) (*model.OutgoingOAuthConnectionGrantType, *model.AppError)); ok {
return rf(rctx, id)
}
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.OutgoingOAuthConnectionGrantType); ok {
r0 = rf(rctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.OutgoingOAuthConnectionGrantType)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
r1 = rf(rctx, id)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetConnections provides a mock function with given fields: rctx, filters
func (_m *OAuthOutgoingConnectionInterface) GetConnections(rctx request.CTX, filters model.OutgoingOAuthConnectionGetConnectionsFilter) ([]*model.OutgoingOAuthConnectionGrantType, *model.AppError) {
ret := _m.Called(rctx, filters)
var r0 []*model.OutgoingOAuthConnectionGrantType
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, model.OutgoingOAuthConnectionGetConnectionsFilter) ([]*model.OutgoingOAuthConnectionGrantType, *model.AppError)); ok {
return rf(rctx, filters)
}
if rf, ok := ret.Get(0).(func(request.CTX, model.OutgoingOAuthConnectionGetConnectionsFilter) []*model.OutgoingOAuthConnectionGrantType); ok {
r0 = rf(rctx, filters)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.OutgoingOAuthConnectionGrantType)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, model.OutgoingOAuthConnectionGetConnectionsFilter) *model.AppError); ok {
r1 = rf(rctx, filters)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// SaveConnection provides a mock function with given fields: rctx, conn
func (_m *OAuthOutgoingConnectionInterface) SaveConnection(rctx request.CTX, conn *model.OutgoingOAuthConnectionGrantType) (*model.OutgoingOAuthConnectionGrantType, *model.AppError) {
ret := _m.Called(rctx, conn)
var r0 *model.OutgoingOAuthConnectionGrantType
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, *model.OutgoingOAuthConnectionGrantType) (*model.OutgoingOAuthConnectionGrantType, *model.AppError)); ok {
return rf(rctx, conn)
}
if rf, ok := ret.Get(0).(func(request.CTX, *model.OutgoingOAuthConnectionGrantType) *model.OutgoingOAuthConnectionGrantType); ok {
r0 = rf(rctx, conn)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.OutgoingOAuthConnectionGrantType)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, *model.OutgoingOAuthConnectionGrantType) *model.AppError); ok {
r1 = rf(rctx, conn)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// UpdateConnection provides a mock function with given fields: rctx, conn
func (_m *OAuthOutgoingConnectionInterface) UpdateConnection(rctx request.CTX, conn *model.OutgoingOAuthConnectionGrantType) (*model.OutgoingOAuthConnectionGrantType, *model.AppError) {
ret := _m.Called(rctx, conn)
var r0 *model.OutgoingOAuthConnectionGrantType
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, *model.OutgoingOAuthConnectionGrantType) (*model.OutgoingOAuthConnectionGrantType, *model.AppError)); ok {
return rf(rctx, conn)
}
if rf, ok := ret.Get(0).(func(request.CTX, *model.OutgoingOAuthConnectionGrantType) *model.OutgoingOAuthConnectionGrantType); ok {
r0 = rf(rctx, conn)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.OutgoingOAuthConnectionGrantType)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, *model.OutgoingOAuthConnectionGrantType) *model.AppError); ok {
r1 = rf(rctx, conn)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
type mockConstructorTestingTNewOAuthOutgoingConnectionInterface interface {
mock.TestingT
Cleanup(func())
}
// NewOAuthOutgoingConnectionInterface creates a new instance of OAuthOutgoingConnectionInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewOAuthOutgoingConnectionInterface(t mockConstructorTestingTNewOAuthOutgoingConnectionInterface) *OAuthOutgoingConnectionInterface {
mock := &OAuthOutgoingConnectionInterface{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

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

@@ -0,0 +1,169 @@
// Code generated by mockery v2.23.2. DO NOT EDIT.
// Regenerate this file using `make einterfaces-mocks`.
package mocks
import (
model "github.com/mattermost/mattermost/server/public/model"
request "github.com/mattermost/mattermost/server/public/shared/request"
mock "github.com/stretchr/testify/mock"
)
// OutgoingOAuthConnectionInterface is an autogenerated mock type for the OutgoingOAuthConnectionInterface type
type OutgoingOAuthConnectionInterface struct {
mock.Mock
}
// DeleteConnection provides a mock function with given fields: rctx, id
func (_m *OutgoingOAuthConnectionInterface) DeleteConnection(rctx request.CTX, id string) *model.AppError {
ret := _m.Called(rctx, id)
var r0 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.AppError); ok {
r0 = rf(rctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AppError)
}
}
return r0
}
// GetConnection provides a mock function with given fields: rctx, id
func (_m *OutgoingOAuthConnectionInterface) GetConnection(rctx request.CTX, id string) (*model.OutgoingOAuthConnection, *model.AppError) {
ret := _m.Called(rctx, id)
var r0 *model.OutgoingOAuthConnection
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, string) (*model.OutgoingOAuthConnection, *model.AppError)); ok {
return rf(rctx, id)
}
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.OutgoingOAuthConnection); ok {
r0 = rf(rctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.OutgoingOAuthConnection)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
r1 = rf(rctx, id)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetConnections provides a mock function with given fields: rctx, filters
func (_m *OutgoingOAuthConnectionInterface) GetConnections(rctx request.CTX, filters model.OutgoingOAuthConnectionGetConnectionsFilter) ([]*model.OutgoingOAuthConnection, *model.AppError) {
ret := _m.Called(rctx, filters)
var r0 []*model.OutgoingOAuthConnection
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, model.OutgoingOAuthConnectionGetConnectionsFilter) ([]*model.OutgoingOAuthConnection, *model.AppError)); ok {
return rf(rctx, filters)
}
if rf, ok := ret.Get(0).(func(request.CTX, model.OutgoingOAuthConnectionGetConnectionsFilter) []*model.OutgoingOAuthConnection); ok {
r0 = rf(rctx, filters)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.OutgoingOAuthConnection)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, model.OutgoingOAuthConnectionGetConnectionsFilter) *model.AppError); ok {
r1 = rf(rctx, filters)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// SanitizeConnection provides a mock function with given fields: conn
func (_m *OutgoingOAuthConnectionInterface) SanitizeConnection(conn *model.OutgoingOAuthConnection) {
_m.Called(conn)
}
// SanitizeConnections provides a mock function with given fields: conns
func (_m *OutgoingOAuthConnectionInterface) SanitizeConnections(conns []*model.OutgoingOAuthConnection) {
_m.Called(conns)
}
// SaveConnection provides a mock function with given fields: rctx, conn
func (_m *OutgoingOAuthConnectionInterface) SaveConnection(rctx request.CTX, conn *model.OutgoingOAuthConnection) (*model.OutgoingOAuthConnection, *model.AppError) {
ret := _m.Called(rctx, conn)
var r0 *model.OutgoingOAuthConnection
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, *model.OutgoingOAuthConnection) (*model.OutgoingOAuthConnection, *model.AppError)); ok {
return rf(rctx, conn)
}
if rf, ok := ret.Get(0).(func(request.CTX, *model.OutgoingOAuthConnection) *model.OutgoingOAuthConnection); ok {
r0 = rf(rctx, conn)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.OutgoingOAuthConnection)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, *model.OutgoingOAuthConnection) *model.AppError); ok {
r1 = rf(rctx, conn)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// UpdateConnection provides a mock function with given fields: rctx, conn
func (_m *OutgoingOAuthConnectionInterface) UpdateConnection(rctx request.CTX, conn *model.OutgoingOAuthConnection) (*model.OutgoingOAuthConnection, *model.AppError) {
ret := _m.Called(rctx, conn)
var r0 *model.OutgoingOAuthConnection
var r1 *model.AppError
if rf, ok := ret.Get(0).(func(request.CTX, *model.OutgoingOAuthConnection) (*model.OutgoingOAuthConnection, *model.AppError)); ok {
return rf(rctx, conn)
}
if rf, ok := ret.Get(0).(func(request.CTX, *model.OutgoingOAuthConnection) *model.OutgoingOAuthConnection); ok {
r0 = rf(rctx, conn)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.OutgoingOAuthConnection)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, *model.OutgoingOAuthConnection) *model.AppError); ok {
r1 = rf(rctx, conn)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
type mockConstructorTestingTNewOutgoingOAuthConnectionInterface interface {
mock.TestingT
Cleanup(func())
}
// NewOutgoingOAuthConnectionInterface creates a new instance of OutgoingOAuthConnectionInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewOutgoingOAuthConnectionInterface(t mockConstructorTestingTNewOutgoingOAuthConnectionInterface) *OutgoingOAuthConnectionInterface {
mock := &OutgoingOAuthConnectionInterface{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

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

@@ -0,0 +1,20 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package einterfaces
import (
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
)
type OutgoingOAuthConnectionInterface interface {
DeleteConnection(rctx request.CTX, id string) *model.AppError
GetConnection(rctx request.CTX, id string) (*model.OutgoingOAuthConnection, *model.AppError)
GetConnections(rctx request.CTX, filters model.OutgoingOAuthConnectionGetConnectionsFilter) ([]*model.OutgoingOAuthConnection, *model.AppError)
SaveConnection(rctx request.CTX, conn *model.OutgoingOAuthConnection) (*model.OutgoingOAuthConnection, *model.AppError)
UpdateConnection(rctx request.CTX, conn *model.OutgoingOAuthConnection) (*model.OutgoingOAuthConnection, *model.AppError)
SanitizeConnection(conn *model.OutgoingOAuthConnection)
SanitizeConnections(conns []*model.OutgoingOAuthConnection)
}

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

@@ -1657,6 +1657,18 @@
"id": "api.context.mfa_required.app_error",
"translation": "Multi-factor authentication is required on this server."
},
{
"id": "api.context.outgoing_oauth_connection.list_connections.app_error",
"translation": "There was an error while listing outgoing OAuth connections."
},
{
"id": "api.context.outgoing_oauth_connection.list_connections.input_error",
"translation": "Invalid input parameters."
},
{
"id": "api.context.outgoing_oauth_connection.not_available.feature_flag",
"translation": "This feature is restricted by a feature flag."
},
{
"id": "api.context.permissions.app_error",
"translation": "You do not have the appropriate permissions."
@@ -8158,6 +8170,30 @@
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user."
},
{
"id": "ent.outgoing_oauth_connections.delete_connection.app_error",
"translation": "There was an error while deleting the outgoing oauth connection."
},
{
"id": "ent.outgoing_oauth_connections.get_connection.app_error",
"translation": "There was an error retrieving the outgoing oauth connection."
},
{
"id": "ent.outgoing_oauth_connections.get_connection.not_found.app_error",
"translation": "The outgoing oauth connection was not found."
},
{
"id": "ent.outgoing_oauth_connections.get_connections.app_error",
"translation": "There was an error retrieving the outgoing oauth connections."
},
{
"id": "ent.outgoing_oauth_connections.save_connection.app_error",
"translation": "There was an error saving the outgoing oauth connection."
},
{
"id": "ent.outgoing_oauth_connections.update_connection.app_error",
"translation": "There was an error updating the outgoing oauth connection."
},
{
"id": "ent.saml.attribute.app_error",
"translation": "SAML login was unsuccessful because one of the attributes is incorrect. Please contact your System Administrator."

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

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