- README.md: Build and deployment guide for beginners - Extend-Function.md: Extended files list and version upgrade guide - Unlock-License.md: License check removal documentation - Dockerfile.local: Local source build support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
540 lines
15 KiB
Markdown
540 lines
15 KiB
Markdown
# 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)
|