1: <?php
2: declare(strict_types=1);
3:
4: /**
5: * +------------------------------------------------------------+
6: * | apnscp |
7: * +------------------------------------------------------------+
8: * | Copyright (c) Apis Networks |
9: * +------------------------------------------------------------+
10: * | Licensed under Artistic License 2.0 |
11: * +------------------------------------------------------------+
12: * | Author: Matt Saladna (msaladna@apisnetworks.com) |
13: * +------------------------------------------------------------+
14: */
15:
16: use Opcenter\Crypto\Keyring;
17: use Symfony\Component\Yaml\Yaml;
18:
19: /**
20: * Class Keyring_Module
21: *
22: * @package core
23: */
24: class Keyring_Module extends \Module_Skeleton
25: {
26: protected $exportedFunctions = [
27: '*' => PRIVILEGE_ADMIN
28: ];
29:
30: /**
31: * Dot-notation key to lookup
32: *
33: * @param string $key
34: * @return bool
35: */
36: public function exists(string $key): bool
37: {
38: if (str_starts_with($key, Keyring::KEYRING_PREFIX)) {
39: $key = substr($key, strlen(Keyring::KEYRING_PREFIX));
40: }
41: $yaml = $this->loadYaml();
42: return array_has($yaml, $key) && Keyring::is((string)array_get($yaml, $key));
43: }
44:
45: /**
46: * Set a keyring value
47: *
48: * @param string $key
49: * @param mixed $value
50: * @param bool $raw
51: * @return bool
52: */
53: public function set(string $key, mixed $value, bool $raw = false): bool
54: {
55: if (str_starts_with($key, Keyring::KEYRING_PREFIX)) {
56: $key = substr($key, strlen(Keyring::KEYRING_PREFIX));
57: }
58:
59: $yaml = $this->loadYaml();
60: array_set($yaml, $key, $raw ? $value : Keyring::encode($value));
61:
62: return false !== file_put_contents(config_path('auth.yaml'), Yaml::dump($yaml));
63: }
64:
65: public function forget(string $key): bool
66: {
67: if (str_starts_with($key, Keyring::KEYRING_PREFIX)) {
68: $key = substr($key, strlen(Keyring::KEYRING_PREFIX));
69: }
70:
71: if (!$this->exists($key)) {
72: return false;
73: }
74:
75: $yaml = $this->loadYaml();
76: array_forget($yaml, $key);
77:
78: return false !== file_put_contents(config_path('auth.yaml'), Yaml::dump($yaml));
79: }
80:
81: /**
82: * Re-encode value by previous secret
83: *
84: * @param string $value
85: * @param string $old_secret
86: * @return string|null
87: */
88: public function reencode(string $value, string $old_secret): ?string
89: {
90: if ($old_secret === Keyring::SECRET) {
91: warn("[%(section)s] => %(option)s is same as %(var)s", [
92: 'section' => 'auth', 'option' => 'secret', 'var' => 'old_secret'
93: ]);
94:
95: return $value;
96: }
97:
98: $oldex = \Error_Reporter::exception_upgrade(\Error_Reporter::E_ERROR);
99: try {
100: $value = Keyring::decode($value, $old_secret);
101: } catch (\apnscpException) {
102: return error("Invalid secret. Cannot decode keyring");
103: } finally {
104: \Error_Reporter::exception_upgrade($oldex);
105: }
106:
107: return Keyring::encode($value);
108: }
109:
110: public function dump(): array
111: {
112: $yaml = $this->loadYaml();
113: $accepted = [];
114: foreach (array_dot($yaml) as $k => $v) {
115: if (is_string($v) && Keyring::is($v)) {
116: array_set($accepted, $k, $v);
117: }
118: }
119:
120: return $accepted;
121: }
122:
123: /**
124: * Fetch raw keyring value
125: *
126: * @param string $key
127: * @return string|null
128: */
129: public function get(string $key): ?string
130: {
131: if (str_starts_with($key, Keyring::KEYRING_PREFIX)) {
132: $key = substr($key, strlen(Keyring::KEYRING_PREFIX));
133: }
134:
135: $yaml = $this->loadYaml();
136: $val = array_get($yaml, $key);
137:
138: return null === $val || !Keyring::is($val) ? null : $val;
139: }
140:
141: /**
142: * Encoded value is valid for this digest
143: *
144: * @param string $value
145: * @return bool
146: */
147: public function valid(string $value): bool
148: {
149: $oldex = \Error_Reporter::exception_upgrade(\Error_Reporter::E_ERROR);
150: try {
151: Keyring::decode($value);
152: return true;
153: } catch (\apnscpException $e) {
154: return false;
155: } finally {
156: \Error_Reporter::exception_upgrade($oldex);
157: }
158: }
159:
160: private function loadYaml(): array
161: {
162: return Yaml::parseFile(config_path('auth.yaml'));
163: }
164: }