scim/pkg/supplier/rocketchat.go
2022-09-23 18:34:45 +02:00

292 lines
7.2 KiB
Go

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
spConfig
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.spConfig.Name == "" {
return "rocketchat"
}
return rc.spConfig.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.removePersonalAccessToken")
if err != nil {
return "", err
}
if resp.IsError() {
return "", fmt.Errorf("failed to remove access token - %s", resp.Body())
}
return rc.generateAccessToken()
}
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
}