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

15 KiB

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 동기화

원본 코드:

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
    }
    // ... 나머지 코드
}

수정 후:

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 연결 테스트

원본 코드:

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
    }
    // ...
}

수정 후:

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 그룹 조회

원본 코드:

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
    }
    // ...
}

수정 후:

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 그룹 연결

원본 코드:

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
    }
    // ...
}

수정 후:

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 그룹 연결 해제

원본 코드:

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
    }
    // ...
}

수정 후:

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 마이그레이션

원본 코드:

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
    }
    // ...
}

수정 후:

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 인증서 생성

원본 코드:

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
    }
    // ...
}

수정 후:

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() - 스키마 생성

원본 코드:

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
    }
    // ...
}

수정 후:

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() - 스키마 수정

원본 코드:

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
    }
    // ...
}

수정 후:

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() - 스키마 삭제

원본 코드:

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
    }
    // ...
}

수정 후:

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() - 팀별 그룹 조회

원본 코드:

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)
    }
    // ...
}

수정 후:

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() - 채널별 그룹 조회

원본 코드:

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)
    }
    // ...
}

수정 후:

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 인증 마이그레이션

원본 코드:

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
    }
    // ...
}

수정 후:

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 인증 마이그레이션

원본 코드:

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
    }
    // ...
}

수정 후:

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를 반환하던 문제를 수정했습니다.

추가된 인터페이스:

// MetricsHandlerProvider is an interface for metrics implementations that can provide an HTTP handler
type MetricsHandlerProvider interface {
    Handler() http.Handler
}

initMetricsRouter()에 추가된 코드:

// 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 코드에서 라이선스 체크는 일반적으로 다음 패턴을 따릅니다:

// 패턴 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 기능에도 라이선스 체크가 있을 수 있습니다. 찾는 방법:

# 모든 라이선스 체크 찾기
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 기능의 전체 지원과 업데이트를 받으려면 공식 라이선스가 필요합니다.


참고 자료