photoprism/internal/pro/config.go
Michael Mayer 46b9239026 Backend: Refactor user entity and add pro package
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
2020-10-03 13:50:30 +02:00

201 lines
3.8 KiB
Go

package pro
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
"github.com/photoprism/photoprism/internal/pro/places"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
"gopkg.in/yaml.v2"
)
// Config represents photoprism.pro api credentials for maps & geodata.
type Config struct {
Key string `json:"key" yaml:"key"`
Secret string `json:"secret" yaml:"secret"`
Session string `json:"session" yaml:"session"`
Status string `json:"status" yaml:"status"`
Version string `json:"version" yaml:"version"`
}
// NewConfig creates a new photoprism.pro api credentials instance.
func NewConfig(version string) *Config {
return &Config{
Key: "",
Secret: "",
Session: "",
Status: "",
Version: version,
}
}
// Propagate updates photoprism.pro api credentials in other packages.
func (p *Config) Propagate() {
places.Key = p.Key
places.Secret = p.Secret
}
// Sanitize verifies and sanitizes photoprism.pro api credentials.
func (p *Config) Sanitize() {
p.Key = strings.ToLower(p.Key)
if p.Secret != "" {
if p.Key != fmt.Sprintf("%x", sha1.Sum([]byte(p.Secret))) {
p.Key = ""
p.Secret = ""
p.Session = ""
p.Status = ""
}
}
}
// DecodeSession decodes photoprism.pro api session data.
func (p *Config) DecodeSession() (Session, error) {
p.Sanitize()
result := Session{}
if p.Session == "" {
return result, fmt.Errorf("empty session")
}
s, err := hex.DecodeString(p.Session)
if err != nil {
return result, err
}
hash := sha256.New()
hash.Write([]byte(p.Secret))
var b []byte
block, err := aes.NewCipher(hash.Sum(b))
if err != nil {
return result, err
}
iv := s[:aes.BlockSize]
plaintext := make([]byte, len(s))
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(plaintext, s[aes.BlockSize:])
plaintext = bytes.Trim(plaintext, "\x00")
if err := json.Unmarshal(plaintext, &result); err != nil {
return result, err
}
return result, nil
}
// Refresh updates photoprism.pro api credentials.
func (p *Config) Refresh() (err error) {
p.Sanitize()
client := &http.Client{Timeout: 60 * time.Second}
url := ApiURL
method := http.MethodPost
var req *http.Request
if p.Key != "" {
url = fmt.Sprintf(ApiURL+"/%s", p.Key)
method = http.MethodPut
log.Debugf("pro: updating api key for maps & places")
} else {
log.Debugf("pro: requesting api key for maps & places")
}
if j, err := json.Marshal(NewRequest(p.Version)); err != nil {
return err
} else if req, err = http.NewRequest(method, url, bytes.NewReader(j)); err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
var r *http.Response
for i := 0; i < 3; i++ {
r, err = client.Do(req)
if err == nil {
break
}
}
if err != nil {
log.Errorf("pro: %s", err.Error())
return err
} else if r.StatusCode >= 400 {
err = fmt.Errorf("api key request for maps & places failed with code %d", r.StatusCode)
return err
}
err = json.NewDecoder(r.Body).Decode(p)
if err != nil {
log.Errorf("pro: %s", err.Error())
return err
}
return nil
}
// Load photoprism.pro api credentials from a YAML file.
func (p *Config) Load(fileName string) error {
if !fs.FileExists(fileName) {
return fmt.Errorf("api key file not found: %s", txt.Quote(fileName))
}
yamlConfig, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
if err := yaml.Unmarshal(yamlConfig, p); err != nil {
return err
}
p.Sanitize()
p.Propagate()
return nil
}
// Save photoprism.pro api credentials to a YAML file.
func (p *Config) Save(fileName string) error {
p.Sanitize()
data, err := yaml.Marshal(p)
if err != nil {
return err
}
p.Propagate()
if err := ioutil.WriteFile(fileName, data, os.ModePerm); err != nil {
return err
}
p.Propagate()
return nil
}