mattermost-community-enterp.../Unlock-License.md
Claude 7e5c7b8c36 Add documentation and local build Dockerfile
- 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>
2025-12-18 01:12:27 +09:00

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)