# Mattermost License Unlock Guide 이 문서는 Mattermost Team Edition에서 Enterprise 기능을 라이선스 없이 사용할 수 있도록 수정한 내용을 상세히 설명합니다. --- ## 개요 Mattermost Enterprise 기능은 두 가지 레벨에서 제어됩니다: 1. **인터페이스 구현체**: Enterprise 기능의 실제 코드 (LDAP, SAML, Cluster 등) 2. **API 라이선스 체크**: REST API에서 라이선스 유무 확인 이 프로젝트는 두 가지를 모두 해결합니다: - Enterprise 구현체를 `enterprise-impl/` 디렉토리에 오픈소스로 제공 - API의 라이선스 체크 코드를 제거 --- ## 라이선스 체크 제거 상세 ### 1. LDAP API (channels/api4/ldap.go) #### 1.1 syncLdap() - LDAP 동기화 **원본 코드:** ```go func syncLdap(c *Context, w http.ResponseWriter, r *http.Request) { if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAP { c.Err = model.NewAppError("Api4.syncLdap", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) return } // ... 나머지 코드 } ``` **수정 후:** ```go func syncLdap(c *Context, w http.ResponseWriter, r *http.Request) { // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionCreateLdapSyncJob) { c.SetPermissionError(model.PermissionCreateLdapSyncJob) return } // ... 나머지 코드 } ``` #### 1.2 testLdap() - LDAP 연결 테스트 **원본 코드:** ```go func testLdap(c *Context, w http.ResponseWriter, r *http.Request) { if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAP { c.Err = model.NewAppError("Api4.testLdap", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func testLdap(c *Context, w http.ResponseWriter, r *http.Request) { // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionTestLdap) { c.SetPermissionError(model.PermissionTestLdap) return } // ... } ``` #### 1.3 getLdapGroups() - LDAP 그룹 조회 **원본 코드:** ```go func getLdapGroups(c *Context, w http.ResponseWriter, r *http.Request) { if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAPGroups { c.Err = model.NewAppError("Api4.getLdapGroups", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func getLdapGroups(c *Context, w http.ResponseWriter, r *http.Request) { // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadUserManagementGroups) { c.SetPermissionError(model.PermissionSysconsoleReadUserManagementGroups) return } // ... } ``` #### 1.4 linkLdapGroup() - LDAP 그룹 연결 **원본 코드:** ```go func linkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireRemoteId() if c.Err != nil { return } if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAPGroups { c.Err = model.NewAppError("Api4.linkLdapGroup", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func linkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireRemoteId() if c.Err != nil { return } // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteUserManagementGroups) { c.SetPermissionError(model.PermissionSysconsoleWriteUserManagementGroups) return } // ... } ``` #### 1.5 unlinkLdapGroup() - LDAP 그룹 연결 해제 **원본 코드:** ```go func unlinkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireRemoteId() if c.Err != nil { return } if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAPGroups { c.Err = model.NewAppError("Api4.unlinkLdapGroup", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func unlinkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireRemoteId() if c.Err != nil { return } // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteUserManagementGroups) { c.SetPermissionError(model.PermissionSysconsoleWriteUserManagementGroups) return } // ... } ``` #### 1.6 migrateIdLdap() - LDAP ID 마이그레이션 **원본 코드:** ```go func migrateIdLdap(c *Context, w http.ResponseWriter, r *http.Request) { if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAP { c.Err = model.NewAppError("Api4.migrateIdLdap", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func migrateIdLdap(c *Context, w http.ResponseWriter, r *http.Request) { // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) { c.SetPermissionError(model.PermissionManageSystem) return } // ... } ``` #### 1.7 createLdapPublicCertificate() - LDAP 인증서 생성 **원본 코드:** ```go func createLdapPublicCertificate(c *Context, w http.ResponseWriter, r *http.Request) { if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAP { c.Err = model.NewAppError("Api4.createLdapPublicCertificate", "api.ldap.license.error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func createLdapPublicCertificate(c *Context, w http.ResponseWriter, r *http.Request) { // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteAuthenticationLdap) { c.SetPermissionError(model.PermissionSysconsoleWriteAuthenticationLdap) return } // ... } ``` --- ### 2. Scheme API (channels/api4/scheme.go) 커스텀 권한 스키마 기능의 라이선스 체크를 제거했습니다. #### 2.1 createScheme() - 스키마 생성 **원본 코드:** ```go func createScheme(c *Context, w http.ResponseWriter, r *http.Request) { // ... if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.CustomPermissionsSchemes { c.Err = model.NewAppError("Api4.CreateScheme", "api.scheme.create_scheme.license.error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func createScheme(c *Context, w http.ResponseWriter, r *http.Request) { // ... // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteUserManagementPermissions) { c.SetPermissionError(model.PermissionSysconsoleWriteUserManagementPermissions) return } // ... } ``` #### 2.2 patchScheme() - 스키마 수정 **원본 코드:** ```go func patchScheme(c *Context, w http.ResponseWriter, r *http.Request) { // ... if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.CustomPermissionsSchemes { c.Err = model.NewAppError("Api4.PatchScheme", "api.scheme.patch_scheme.license.error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func patchScheme(c *Context, w http.ResponseWriter, r *http.Request) { // ... // Community Enterprise: License check removed for open source usage // ... if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteUserManagementPermissions) { c.SetPermissionError(model.PermissionSysconsoleWriteUserManagementPermissions) return } // ... } ``` #### 2.3 deleteScheme() - 스키마 삭제 **원본 코드:** ```go func deleteScheme(c *Context, w http.ResponseWriter, r *http.Request) { // ... if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.CustomPermissionsSchemes { c.Err = model.NewAppError("Api4.DeleteScheme", "api.scheme.delete_scheme.license.error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func deleteScheme(c *Context, w http.ResponseWriter, r *http.Request) { // ... // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteUserManagementPermissions) { c.SetPermissionError(model.PermissionSysconsoleWriteUserManagementPermissions) return } // ... } ``` --- ### 3. Group API (channels/api4/group.go) 팀/채널 그룹 기능의 라이선스 체크를 제거했습니다. #### 3.1 getGroupsByTeamCommon() - 팀별 그룹 조회 **원본 코드:** ```go func getGroupsByTeamCommon(c *Context, r *http.Request) ([]byte, *model.AppError) { // ... if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAPGroups { return nil, model.NewAppError("Api4.getGroupsByTeam", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) } // ... } ``` **수정 후:** ```go func getGroupsByTeamCommon(c *Context, r *http.Request) ([]byte, *model.AppError) { // ... // Community Enterprise: License check removed for open source usage if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionViewTeam) { return nil, c.App.MakePermissionError(c.AppContext.Session(), []*model.Permission{model.PermissionViewTeam}) } // ... } ``` #### 3.2 getGroupsByChannelCommon() - 채널별 그룹 조회 **원본 코드:** ```go func getGroupsByChannelCommon(c *Context, r *http.Request) ([]byte, *model.AppError) { // ... if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAPGroups { return nil, model.NewAppError("Api4.getGroupsByChannel", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) } // ... } ``` **수정 후:** ```go func getGroupsByChannelCommon(c *Context, r *http.Request) ([]byte, *model.AppError) { // ... // Community Enterprise: License check removed for open source usage // Permission check follows... // ... } ``` --- ### 4. User API (channels/api4/user.go) 사용자 인증 마이그레이션의 라이선스 체크를 제거했습니다. #### 4.1 migrateAuthToLdap() - LDAP 인증 마이그레이션 **원본 코드:** ```go func migrateAuthToLdap(c *Context, w http.ResponseWriter, r *http.Request) { // ... if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAP { c.Err = model.NewAppError("Api4.migrateAuthToLdap", "api.user.migrate_auth.license.app_error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func migrateAuthToLdap(c *Context, w http.ResponseWriter, r *http.Request) { // ... // Community Enterprise: License check removed for open source usage // Email auth in Mattermost system is represented by "" // ... } ``` #### 4.2 migrateAuthToSaml() - SAML 인증 마이그레이션 **원본 코드:** ```go func migrateAuthToSaml(c *Context, w http.ResponseWriter, r *http.Request) { // ... if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.SAML { c.Err = model.NewAppError("Api4.migrateAuthToSaml", "api.user.migrate_auth.license.app_error", nil, "", http.StatusForbidden) return } // ... } ``` **수정 후:** ```go func migrateAuthToSaml(c *Context, w http.ResponseWriter, r *http.Request) { // ... // Community Enterprise: License check removed for open source usage if from == "email" { from = "" } // ... } ``` --- ### 5. Metrics (channels/app/platform/metrics.go) Prometheus 메트릭 엔드포인트가 404를 반환하던 문제를 수정했습니다. **추가된 인터페이스:** ```go // MetricsHandlerProvider is an interface for metrics implementations that can provide an HTTP handler type MetricsHandlerProvider interface { Handler() http.Handler } ``` **initMetricsRouter()에 추가된 코드:** ```go // Register /metrics handler if metrics implementation provides a handler if pm.metricsImpl != nil { if handlerProvider, ok := pm.metricsImpl.(MetricsHandlerProvider); ok { pm.router.Handle("/metrics", handlerProvider.Handler()) pm.logger.Info("Metrics endpoint registered at /metrics") } } ``` --- ## 라이선스 체크 패턴 Mattermost 코드에서 라이선스 체크는 일반적으로 다음 패턴을 따릅니다: ```go // 패턴 1: 단순 nil 체크 if c.App.Channels().License() == nil { c.Err = model.NewAppError(...) return } // 패턴 2: nil 체크 + 기능 플래그 if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAP { c.Err = model.NewAppError(...) return } // 패턴 3: 기능 플래그만 체크 if !*c.App.Channels().License().Features.CustomPermissionsSchemes { c.Err = model.NewAppError(...) return } ``` ### 제거 방법 1. 라이선스 체크 조건문 전체를 삭제 2. 주석으로 표시: `// Community Enterprise: License check removed for open source usage` 3. 권한 체크만 남김 (보안 유지) --- ## 추가 라이선스 체크 위치 다른 Enterprise 기능에도 라이선스 체크가 있을 수 있습니다. 찾는 방법: ```bash # 모든 라이선스 체크 찾기 grep -r "License()" channels/api4/ | grep -v "_test.go" # 특정 기능의 라이선스 체크 grep -r "Features.SAML" channels/api4/ grep -r "Features.Cluster" channels/api4/ grep -r "Features.Compliance" channels/api4/ grep -r "Features.DataRetention" channels/api4/ grep -r "Features.MessageExport" channels/api4/ ``` --- ## UI 라이선스 체크 웹 클라이언트(React)에도 라이선스 체크가 있습니다: ``` client/ ├── *.js # Minified JavaScript └── *.js.map # Source maps ``` JavaScript 파일은 minified 상태라 직접 수정이 어렵습니다. ### 해결 방법 옵션 1. **webapp 소스 빌드**: mattermost/mattermost-webapp 저장소에서 소스를 수정하고 다시 빌드 2. **API만 사용**: API는 라이선스 체크가 제거되었으므로 CLI나 API 호출로 기능 사용 3. **커스텀 플러그인**: 웹 UI를 우회하는 플러그인 개발 --- ## 면책 조항 이 수정은 교육 및 개인 학습 목적으로 제공됩니다. 상용 환경에서는 Mattermost 공식 라이선스를 구매하시기 바랍니다. Enterprise 기능의 전체 지원과 업데이트를 받으려면 공식 라이선스가 필요합니다. --- ## 참고 자료 - [Mattermost 공식 문서](https://docs.mattermost.com/) - [Mattermost GitHub](https://github.com/mattermost/mattermost) - [einterfaces 정의](https://github.com/mattermost/mattermost/tree/master/server/channels/einterfaces)