2020-10-03 13:50:30 +02:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
// MapKey returns the maps api key.
|
|
|
|
func (c *Config) MapKey() string {
|
|
|
|
if sess, err := c.DecodeSession(); err != nil {
|
|
|
|
return ""
|
|
|
|
} else {
|
|
|
|
return sess.MapKey
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-03 13:50:30 +02:00
|
|
|
// Propagate updates photoprism.pro api credentials in other packages.
|
2020-10-04 04:47:54 +02:00
|
|
|
func (c *Config) Propagate() {
|
|
|
|
places.Key = c.Key
|
|
|
|
places.Secret = c.Secret
|
2020-10-03 13:50:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sanitize verifies and sanitizes photoprism.pro api credentials.
|
2020-10-04 04:47:54 +02:00
|
|
|
func (c *Config) Sanitize() {
|
|
|
|
c.Key = strings.ToLower(c.Key)
|
|
|
|
|
|
|
|
if c.Secret != "" {
|
|
|
|
if c.Key != fmt.Sprintf("%x", sha1.Sum([]byte(c.Secret))) {
|
|
|
|
c.Key = ""
|
|
|
|
c.Secret = ""
|
|
|
|
c.Session = ""
|
|
|
|
c.Status = ""
|
2020-10-03 13:50:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// DecodeSession decodes photoprism.pro api session data.
|
2020-10-04 04:47:54 +02:00
|
|
|
func (c *Config) DecodeSession() (Session, error) {
|
|
|
|
c.Sanitize()
|
2020-10-03 13:50:30 +02:00
|
|
|
|
|
|
|
result := Session{}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
if c.Session == "" {
|
2020-10-03 13:50:30 +02:00
|
|
|
return result, fmt.Errorf("empty session")
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
s, err := hex.DecodeString(c.Session)
|
2020-10-03 13:50:30 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
hash := sha256.New()
|
2020-10-04 04:47:54 +02:00
|
|
|
hash.Write([]byte(c.Secret))
|
2020-10-03 13:50:30 +02:00
|
|
|
|
|
|
|
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.
|
2020-10-04 04:47:54 +02:00
|
|
|
func (c *Config) Refresh() (err error) {
|
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
|
|
|
|
|
|
|
c.Sanitize()
|
2020-10-03 13:50:30 +02:00
|
|
|
client := &http.Client{Timeout: 60 * time.Second}
|
|
|
|
url := ApiURL
|
|
|
|
method := http.MethodPost
|
|
|
|
var req *http.Request
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
if c.Key != "" {
|
|
|
|
url = fmt.Sprintf(ApiURL+"/%s", c.Key)
|
2020-10-03 13:50:30 +02:00
|
|
|
method = http.MethodPut
|
|
|
|
log.Debugf("pro: updating api key for maps & places")
|
|
|
|
} else {
|
|
|
|
log.Debugf("pro: requesting api key for maps & places")
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
if j, err := json.Marshal(NewRequest(c.Version)); err != nil {
|
2020-10-03 13:50:30 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
err = json.NewDecoder(r.Body).Decode(c)
|
2020-10-03 13:50:30 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("pro: %s", err.Error())
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load photoprism.pro api credentials from a YAML file.
|
2020-10-04 04:47:54 +02:00
|
|
|
func (c *Config) Load(fileName string) error {
|
2020-10-03 13:50:30 +02:00
|
|
|
if !fs.FileExists(fileName) {
|
|
|
|
return fmt.Errorf("api key file not found: %s", txt.Quote(fileName))
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
|
|
|
|
2020-10-03 13:50:30 +02:00
|
|
|
yamlConfig, err := ioutil.ReadFile(fileName)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
if err := yaml.Unmarshal(yamlConfig, c); err != nil {
|
2020-10-03 13:50:30 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
c.Sanitize()
|
|
|
|
c.Propagate()
|
2020-10-03 13:50:30 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save photoprism.pro api credentials to a YAML file.
|
2020-10-04 04:47:54 +02:00
|
|
|
func (c *Config) Save(fileName string) error {
|
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
|
|
|
|
|
|
|
c.Sanitize()
|
2020-10-03 13:50:30 +02:00
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
data, err := yaml.Marshal(c)
|
2020-10-03 13:50:30 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
c.Propagate()
|
2020-10-03 13:50:30 +02:00
|
|
|
|
|
|
|
if err := ioutil.WriteFile(fileName, data, os.ModePerm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:47:54 +02:00
|
|
|
c.Propagate()
|
2020-10-03 13:50:30 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|