Files
oneterm/backend/acl/auth.go
2024-09-05 10:01:17 +08:00

182 lines
4.1 KiB
Go

package acl
import (
"bytes"
"compress/zlib"
"crypto/hmac"
"crypto/sha1"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"hash"
"io"
"reflect"
"strings"
"github.com/veops/oneterm/conf"
"github.com/veops/oneterm/remote"
)
// SigningAlgorithm provides interfaces to generate and verify signature
type SigningAlgorithm interface {
GetSignature(key, value string) []byte
VerifySignature(key, value string, sig []byte) bool
}
// HMACAlgorithm provides signature generation using HMACs.
type HMACAlgorithm struct {
DigestMethod func() hash.Hash
}
// GetSignature returns the signature for the given key and value.
func (a *HMACAlgorithm) GetSignature(key, value string) []byte {
//a.DigestMethod().Reset()
h := hmac.New(a.DigestMethod, []byte(key))
h.Write([]byte(value))
return h.Sum(nil)
}
// VerifySignature verifies the given signature matches the expected signature.
func (a *HMACAlgorithm) VerifySignature(key, value string, sig []byte) bool {
eq := subtle.ConstantTimeCompare(sig, []byte(a.GetSignature(key, value)))
return eq == 1
}
type Signature struct {
SecretKey string
Sep string
Salt string
KeyDerivation string
DigestMethod func() hash.Hash
Algorithm SigningAlgorithm
}
// Unsign the given string.
func (s *Signature) Unsign(signed string) (content []byte, err error) {
if !strings.Contains(signed, s.Sep) {
err = fmt.Errorf("no %s found in value", s.Sep)
return
}
li := strings.LastIndex(signed, s.Sep)
value, sig := signed[:li], signed[li+len(s.Sep):]
if ok, _ := s.Verify(value, sig); ok {
//c, err := base64Decode(strings.Split(strings.Trim(value, "."), ".")[0])
var c []byte
c, err = base64.RawURLEncoding.DecodeString(strings.Split(strings.Trim(value, "."), ".")[0])
if err != nil {
return
}
var r io.ReadCloser
r, err = zlib.NewReader(bytes.NewReader(c))
if err != nil {
return
}
return io.ReadAll(r)
}
err = fmt.Errorf("signature %s does not match", sig)
return
}
func (s *Signature) Verify(value, sig string) (bool, error) {
key, err := s.DeriveKey()
if err != nil {
return false, err
}
signed, err := base64.RawURLEncoding.DecodeString(sig)
if err != nil {
return false, err
}
return s.Algorithm.VerifySignature(key, value, signed), nil
}
func (s *Signature) DeriveKey() (string, error) {
var key string
var err error
switch s.KeyDerivation {
case "hmac":
h := hmac.New(sha1.New, []byte(s.SecretKey))
h.Write([]byte(s.Salt))
key = string(h.Sum(nil))
case "none":
key = s.SecretKey
default:
key, err = "", errors.New("unknown key derivation method")
}
return key, err
}
func NewSignature(secret, salt, sep, derivation string, digest func() hash.Hash, algo SigningAlgorithm) *Signature {
if salt == "" {
salt = "itsdangerous.Signer"
}
if sep == "" {
sep = "."
}
if derivation == "" {
derivation = "hmac"
}
if digest == nil {
digest = sha1.New
}
if algo == nil {
algo = &HMACAlgorithm{DigestMethod: digest}
}
return &Signature{
SecretKey: secret,
Salt: salt,
Sep: sep,
KeyDerivation: derivation,
DigestMethod: digest,
Algorithm: algo,
}
}
func AuthWithKey(path string, originData map[string]any) (sess *Session, err error) {
body := map[string]any{
"path": path,
"key": originData["_key"],
"secret": originData["_secret"],
"need_parentRoles": true,
"app_id": "oneterm",
}
payload := make(map[string]any)
for k, v := range originData {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Map || rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
continue
}
payload[k] = v
}
body["payload"] = payload
url := fmt.Sprintf("%s%s", conf.Cfg.Auth.Acl.Url, "/acl/auth_with_key")
data := &AuthWithKeyResp{}
resp, err := remote.RC.R().
SetBody(body).
SetResult(data).
Post(url)
if err = remote.HandleErr(err, resp, nil); err == nil {
sess = &Session{
Uid: data.User.UID,
Acl: Acl{
Uid: data.User.UID,
UserName: data.User.Username,
Rid: data.User.Rid,
NickName: data.User.Name,
ParentRoles: data.User.ParentRoles,
},
}
}
return
}