[CLD-6324] Cloud IP Filtering (#24726)
* Initial comit for ip filtering service implementation * Add audit logs for IP Filters * start of webapp work * Stashing * Updates based on Agniva's feedback around service vs einterface * Updates completed * Commit before refactoring, everything's working * First pass of cleanup complete, front-end tests added * actually add files * Updates to some translation strings, running i18n-extract * Lock everything behind a feature flag * Fix tests, try to fix some linter stuff * Fixed linter for JS, on to scss * Fixed linter for scss * Fix linter * More fixes for pipeline * Support for IPV6 * Remove tsx file that was removed in masteR * Revert package.json and package-lock.json to master, add cidr-regex dep into channels/package.json * Another commit to force fix Github * Fixes around IPV6. Some suggestions from Matt re: UX review. Fixing pipelines for tests and types on new cidr-regex package * Changes to address Matt's feedback * A few more changes for clean up * Add support for permissions * Fix vet for OpenAPI spec * Actually add the yaml file for openapi * Add permission migration to allow support for IP Filtering * Fix tests * Final fixes from Matt * Remove cancel button from page, update link outs to documentation * Update test to account for removed cancel button * Adjustments based on feedback from Harrison * More fixes from PR feedback * Add a t to fix translations that doesn't seem to be breaking anyone else? * More fix * updates for PR feedback * Fix linter * Fix types * Now fix the linter again * Add back tests because Harrison was able to get them running * Adjustments for PR feedback * Remove admin_definition.jsx * Fix linter * [CLD-6453] IP Filtering notification email for sysadmins (#25224) * Initial commit for IP filtering alert email * Updates to style for email, addition of ip_filtering email: * Fix pipelines * Adjustments from Matt's feedback * Padding changes * template diff (#25249) Co-authored-by: Gabe Jackson <3694686+gabrieljackson@users.noreply.github.com> * Fix hardcoded true, remove bool return value --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Gabe Jackson <3694686+gabrieljackson@users.noreply.github.com> * Lock feature behind enterprise license. Drop cidr-regex in favour of ipaddr.js dependency. Refactor isIpAddressWithinRanges to use ipaddr.js * Add a couple server tests * fix linter * Fix types from merge conflicts --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Gabe Jackson <3694686+gabrieljackson@users.noreply.github.com>
Этот коммит содержится в:
@@ -103,6 +103,11 @@ 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) HTTPService() httpservice.HTTPService {
|
||||
return a.ch.srv.httpService
|
||||
}
|
||||
|
||||
@@ -868,6 +868,7 @@ type AppIface interface {
|
||||
HasPermissionToTeam(c request.CTX, askingUserId string, teamID string, permission *model.Permission) bool
|
||||
HasPermissionToUser(askingUserId string, userID string) bool
|
||||
HasSharedChannel(channelID string) (bool, error)
|
||||
IPFiltering() einterfaces.IPFilteringInterface
|
||||
ImageProxy() *imageproxy.ImageProxy
|
||||
ImageProxyAdder() func(string) string
|
||||
ImageProxyRemover() (f func(string) string)
|
||||
|
||||
@@ -1275,3 +1275,40 @@ func (es *Service) SendRemoveExpiredLicenseEmail(ctaText, ctaLink, email, locale
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendIPFiltersChangedEmail(email string, initiatingUser *model.User, siteURL, portalURL, locale string, isWorkspaceOwner bool) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
|
||||
subject := T("api.templates.ip_filters_changed.subject")
|
||||
|
||||
data := es.NewEmailTemplateData(locale)
|
||||
data.Props["SiteURL"] = siteURL
|
||||
data.Props["Title"] = T("api.templates.ip_filters_changed.title")
|
||||
data.Props["SubTitle"] = T("api.templates.ip_filters_changed.subTitle", map[string]any{"InitiatingUsername": initiatingUser.Username, "SiteURL": siteURL})
|
||||
data.Props["ButtonURL"] = siteURL + "/admin_console/site_config/ip_filtering"
|
||||
data.Props["Button"] = T("api.templates.ip_filters_changed.button")
|
||||
data.Props["TroubleAccessingTitle"] = T("api.templates.ip_filters_changed_footer.title")
|
||||
data.Props["SendAnEmailTo"] = T("api.templates.ip_filters_changed_footer.send_an_email_to", map[string]any{"InitiatingUserEmail": initiatingUser.Email})
|
||||
data.Props["PortalURL"] = portalURL
|
||||
// If the email we're sending to was the one who initiated the change, we don't want to show their email address as a mailto
|
||||
if email != initiatingUser.Email {
|
||||
data.Props["ActorEmail"] = initiatingUser.Email
|
||||
}
|
||||
|
||||
if isWorkspaceOwner {
|
||||
data.Props["LogInToCustomerPortal"] = T("api.templates.ip_filters_changed_footer.log_in_to_customer_portal")
|
||||
}
|
||||
data.Props["ContactSupport"] = T("api.templates.ip_filters_changed_footer.contact_support")
|
||||
data.Props["SupportEmail"] = *es.config().SupportSettings.SupportEmail
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("ip_filters_changed", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body, "PasswordResetEmail"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -310,6 +310,20 @@ func (_m *ServiceInterface) SendGuestInviteEmails(team *model.Team, channels []*
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendIPFiltersChangedEmail provides a mock function with given fields: _a0, userWhoChangedFilter, siteURL, portalURL, locale, isWorkspaceOwner
|
||||
func (_m *ServiceInterface) SendIPFiltersChangedEmail(_a0 string, userWhoChangedFilter *model.User, siteURL string, portalURL string, locale string, isWorkspaceOwner bool) error {
|
||||
ret := _m.Called(_a0, userWhoChangedFilter, siteURL, portalURL, locale, isWorkspaceOwner)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, *model.User, string, string, string, bool) error); ok {
|
||||
r0 = rf(_a0, userWhoChangedFilter, siteURL, portalURL, locale, isWorkspaceOwner)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SendInviteEmails provides a mock function with given fields: team, senderName, senderUserId, invites, siteURL, reminderData, errorWhenNotSent, isSystemAdmin, isFirstAdmin
|
||||
func (_m *ServiceInterface) SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string, reminderData *model.TeamInviteReminderData, errorWhenNotSent bool, isSystemAdmin bool, isFirstAdmin bool) error {
|
||||
ret := _m.Called(team, senderName, senderUserId, invites, siteURL, reminderData, errorWhenNotSent, isSystemAdmin, isFirstAdmin)
|
||||
|
||||
@@ -163,6 +163,7 @@ type ServiceInterface interface {
|
||||
InitEmailBatching()
|
||||
SendChangeUsernameEmail(newUsername, email, locale, siteURL string) error
|
||||
CreateVerifyEmailToken(userID string, newEmail string) (*model.Token, error)
|
||||
SendIPFiltersChangedEmail(email string, userWhoChangedFilter *model.User, siteURL, portalURL, locale string, isWorkspaceOwner bool) error
|
||||
Stop()
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,12 @@ func RegisterNotificationInterface(f func(*App) einterfaces.NotificationInterfac
|
||||
notificationInterface = f
|
||||
}
|
||||
|
||||
var ipFilteringInterface func(*App) einterfaces.IPFilteringInterface
|
||||
|
||||
func RegisterIPFilteringInterface(f func(*App) einterfaces.IPFilteringInterface) {
|
||||
ipFilteringInterface = f
|
||||
}
|
||||
|
||||
func (s *Server) initEnterprise() {
|
||||
if cloudInterface != nil {
|
||||
s.Cloud = cloudInterface(s)
|
||||
|
||||
@@ -11560,6 +11560,23 @@ func (a *OpenTracingAppLayer) HubUnregister(webConn *platform.WebConn) {
|
||||
a.app.HubUnregister(webConn)
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) IPFiltering() einterfaces.IPFilteringInterface {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.IPFiltering")
|
||||
|
||||
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.IPFiltering()
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) ImageProxyAdder() func(string) string {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ImageProxyAdder")
|
||||
|
||||
@@ -1117,6 +1117,30 @@ func (a *App) getAddChannelReadContentPermissions() (permissionsMap, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (a *App) getAddIPFilterPermissionsMigration() (permissionsMap, error) {
|
||||
t := []permissionTransformation{}
|
||||
|
||||
ipFilterPermissionsRead := []string{
|
||||
model.PermissionSysconsoleReadIPFilters.Id,
|
||||
}
|
||||
|
||||
ipFilterPermissionsWrite := []string{
|
||||
model.PermissionSysconsoleWriteIPFilters.Id,
|
||||
}
|
||||
|
||||
t = append(t, permissionTransformation{
|
||||
On: permissionOr(isExactRole(model.SystemAdminRoleId)),
|
||||
Add: ipFilterPermissionsRead,
|
||||
})
|
||||
|
||||
t = append(t, permissionTransformation{
|
||||
On: permissionOr(isExactRole(model.SystemAdminRoleId)),
|
||||
Add: ipFilterPermissionsWrite,
|
||||
})
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// DoPermissionsMigrations execute all the permissions migrations need by the current version.
|
||||
func (a *App) DoPermissionsMigrations() error {
|
||||
return a.Srv().doPermissionsMigrations()
|
||||
@@ -1161,6 +1185,7 @@ func (s *Server) doPermissionsMigrations() error {
|
||||
{Key: model.MigrationKeyAddProductsBoardsPermissions, Migration: a.getProductsBoardsPermissions},
|
||||
{Key: model.MigrationKeyAddCustomUserGroupsPermissionRestore, Migration: a.getAddCustomUserGroupsPermissionRestore},
|
||||
{Key: model.MigrationKeyAddReadChannelContentPermissions, Migration: a.getAddChannelReadContentPermissions},
|
||||
{Key: model.MigrationKeyAddIPFilteringPermissions, Migration: a.getAddIPFilterPermissionsMigration},
|
||||
}
|
||||
|
||||
roles, err := s.Store().Role().GetAll()
|
||||
|
||||
@@ -141,7 +141,8 @@ type Server struct {
|
||||
// startSearchEngine bool
|
||||
skipPostInit bool
|
||||
|
||||
Cloud einterfaces.CloudInterface
|
||||
Cloud einterfaces.CloudInterface
|
||||
IPFiltering einterfaces.IPFilteringInterface
|
||||
|
||||
tracer *tracing.Tracer
|
||||
|
||||
@@ -396,6 +397,10 @@ func NewServer(options ...Option) (*Server, error) {
|
||||
|
||||
s.initJobs()
|
||||
|
||||
if ipFilteringInterface != nil {
|
||||
s.IPFiltering = ipFilteringInterface(app)
|
||||
}
|
||||
|
||||
s.clusterLeaderListenerId = s.AddClusterLeaderChangedListener(func() {
|
||||
mlog.Info("Cluster leader changed. Determining if job schedulers should be running:", mlog.Bool("isLeader", s.IsLeader()))
|
||||
if s.Jobs != nil {
|
||||
|
||||
Ссылка в новой задаче
Block a user