package supplier import ( "context" "crypto/sha256" "encoding/hex" "errors" "fmt" "io/ioutil" "strings" "github.com/go-resty/resty/v2" "github.com/tidwall/gjson" ) const setting_AppFrameworkDevelopementMode = "Apps_Framework_Development_Mode" const setting_AppFrameworkEnabled = "Apps_Framework_enabled" const rocketchat_app_id = "53fd430f-3924-4c6c-8774-b23fabfeb0e5" type rocketchat struct { *resty.Client ServiceProviderConfig ctx context.Context sideLoading bool previousAppDevMode bool } func (rc *rocketchat) StartSession(ctx context.Context) error { rc.ctx = ctx if err := rc.login(); err != nil { return err } err := rc.setSetting(setting_AppFrameworkEnabled, true) if err != nil { return err } if rc.sideLoading { appDevMode, err := rc.getSetting(setting_AppFrameworkDevelopementMode) if err != nil { return err } rc.previousAppDevMode = appDevMode.Bool() if !rc.previousAppDevMode { rc.setSetting(setting_AppFrameworkDevelopementMode, true) } } return nil } func (rc *rocketchat) CloseSession() error { if rc.sideLoading && !rc.previousAppDevMode { if err := rc.setSetting(setting_AppFrameworkDevelopementMode, rc.previousAppDevMode); err != nil { return err } } return nil } func (rc *rocketchat) InstallOrUpdate() error { installedVersion, err := rc.getInstalledVersion() if err != nil { return err } version, err := rc.getVersion() if err != nil { return err } if rc.sideLoading && installedVersion != version { uri := "/api/apps" if installedVersion != "" { uri = uri + "/update" } if err := rc.download(version); err != nil { return err } resp, err := rc.R().SetFile("app", "scim.zip").Post(uri) if err != nil { return err } if resp.IsError() { return fmt.Errorf("fail to upload app - %s", resp.Body()) } } return nil } func (rc *rocketchat) Enable() error { enabled, err := rc.isEnabled() if err != nil { return err } if !enabled { body := map[string]string{ "status": "manually_enabled", } resp, err := rc.R().SetBody(body).Post(fmt.Sprintf("/api/apps/%s/status", rocketchat_app_id)) if err != nil { return err } if resp.IsError() { return fmt.Errorf("fail to enable app - %s", resp.Body()) } } return nil } func (rc *rocketchat) GenerateCredentials() (string, string, error) { username, err := rc.userId() if err != nil { return "", "", err } exists, err := rc.accessTokenExists() if err != nil { return "", "", err } var token string if exists { token, err = rc.regenerateAccessToken() } else { token, err = rc.generateAccessToken() } if err != nil { return "", "", err } return username, token, nil } func (rc *rocketchat) ScimEndpoint() string { return fmt.Sprintf("%s/api/apps/public/53fd430f-3924-4c6c-8774-b23fabfeb0e5/", rc.Host) } func (rc *rocketchat) Name() string { if rc.ServiceProviderConfig.Name == "" { return "rocketchat" } return rc.ServiceProviderConfig.Name } func (rc *rocketchat) login() error { body := map[string]string{ "user": rc.Username, "password": rc.Password, } resp, err := rc.R().SetBody(body).Post("/api/v1/login") if err != nil { return err } if resp.IsError() { return errors.New(string(resp.Body())) } data := gjson.GetMany(string(resp.Body()), "data.userId", "data.authToken") hashedPass := sha256.Sum256([]byte(rc.Password)) rc.Client = rc.Client. SetHeader("X-User-Id", data[0].String()). SetHeader("X-Auth-Token", data[1].String()). SetHeader("X-2fa-code", hex.EncodeToString(hashedPass[:])). SetHeader("X-2fa-method", "password") return nil } func (rc *rocketchat) getSetting(key string) (gjson.Result, error) { resp, err := rc.R().Get(fmt.Sprintf("/api/v1/settings/%s", key)) if err != nil { return gjson.Result{}, err } if resp.IsError() { return gjson.Result{}, fmt.Errorf("fail to get setting %s - %s", key, resp.Body()) } return gjson.Get(string(resp.Body()), "value"), nil } func (rc *rocketchat) setSetting(key string, value interface{}) error { body := map[string]interface{}{} body["value"] = value resp, err := rc.R(). SetContext(rc.ctx). SetBody(body). Post(fmt.Sprintf("/api/v1/settings/%s", key)) if err != nil { return err } if resp.IsError() { return fmt.Errorf("fail to set setting %s - %s", key, resp.Body()) } return nil } func (rc *rocketchat) getVersion() (string, error) { if rc.Version != "" { return rc.Version, nil } resp, err := resty.New().R().SetContext(rc.ctx).Get("https://lab.libreho.st/api/v4/projects/207/releases") if err != nil { return "", err } if resp.IsError() { return "", fmt.Errorf("fail to get releases - %s", resp.Body()) } rc.Version = gjson.Get(string(resp.Body()), "0.tag_name").String() return rc.Version, nil } func (rc *rocketchat) download(version string) error { resp, err := resty.New().R().SetContext(rc.ctx).Get(fmt.Sprintf("https://lab.libreho.st/api/v4/projects/207/packages/generic/scim/%s/scim.zip", version)) if err != nil { return err } if resp.IsError() { return fmt.Errorf("fail to download package - %s", resp.Body()) } rc.Version = gjson.Get(string(resp.Body()), "0.tag_name").String() return ioutil.WriteFile("scim.zip", resp.Body(), 0644) } func (rc *rocketchat) getAppInfo() (string, error) { resp, err := rc.R().Get(fmt.Sprintf("/api/apps/%s", rocketchat_app_id)) if err != nil { return "", err } return string(resp.Body()), nil } func (rc *rocketchat) getInstalledVersion() (string, error) { raw, err := rc.getAppInfo() if err != nil { return "", err } return gjson.Get(raw, "app.version").String(), nil } func (rc *rocketchat) isEnabled() (bool, error) { raw, err := rc.getAppInfo() if err != nil { return false, err } status := gjson.Get(raw, "app.status").String() return strings.Contains(status, "enabled"), nil } func (rc *rocketchat) accessTokenExists() (bool, error) { resp, err := rc.R().Get("/api/v1/users.getPersonalAccessTokens") if err != nil { return false, err } if resp.IsError() { return false, fmt.Errorf("failed to get access tokens - %s", resp.Body()) } return gjson.Get(string(resp.Body()), `tokens.#(name=="scim")`).Exists(), nil } func (rc *rocketchat) regenerateAccessToken() (string, error) { body := map[string]string{"tokenName": "scim"} resp, err := rc.R().SetBody(body).Post("/api/v1/users.regeneratePersonalAccessToken") if err != nil { return "", err } if resp.IsError() { return "", fmt.Errorf("failed to regenerate access token - %s", resp.Body()) } return gjson.Get(string(resp.Body()), "token").String(), nil } func (rc *rocketchat) generateAccessToken() (string, error) { body := map[string]interface{}{ "tokenName": "scim", "bypassTwoFactor": true, } resp, err := rc.R().SetBody(body).Post("/api/v1/users.generatePersonalAccessToken") if err != nil { return "", err } if resp.IsError() { return "", fmt.Errorf("failed to generate access token - %s", resp.Body()) } return gjson.Get(string(resp.Body()), "token").String(), nil } func (rc *rocketchat) R() *resty.Request { return rc.Client.R().SetContext(rc.ctx) } func (rc *rocketchat) userId() (string, error) { resp, err := rc.R().Get("/api/v1/me") if err != nil { return "", err } if resp.IsError() { return "", fmt.Errorf("failed to get user - %s", resp.Body()) } return gjson.Get(string(resp.Body()), "_id").String(), nil }