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: /**
111: * Create a keyring literal
112: *
113: * @param string $value
114: * @return string
115: */
116: public function encode(mixed $value): string
117: {
118: return Keyring::encode($value);
119: }
120:
121: public function dump(): array
122: {
123: $yaml = $this->loadYaml();
124: $accepted = [];
125: foreach (array_dot($yaml) as $k => $v) {
126: if (is_string($v) && Keyring::is($v)) {
127: array_set($accepted, $k, $v);
128: }
129: }
130:
131: return $accepted;
132: }
133:
134: /**
135: * Fetch raw keyring value
136: *
137: * @param string $key
138: * @return string|null
139: */
140: public function get(string $key): ?string
141: {
142: if (str_starts_with($key, Keyring::KEYRING_PREFIX)) {
143: $key = substr($key, strlen(Keyring::KEYRING_PREFIX));
144: }
145:
146: $yaml = $this->loadYaml();
147: $val = array_get($yaml, $key);
148:
149: return null === $val || !Keyring::is((string)$val) ? null : $val;
150: }
151:
152: /**
153: * Encoded value is valid for this digest
154: *
155: * @param string $value
156: * @return bool
157: */
158: public function valid(string $value): bool
159: {
160: $oldex = \Error_Reporter::exception_upgrade(\Error_Reporter::E_ERROR);
161: try {
162: Keyring::decode($value);
163: return true;
164: } catch (\apnscpException $e) {
165: return false;
166: } finally {
167: \Error_Reporter::exception_upgrade($oldex);
168: }
169: }
170:
171: private function loadYaml(): array
172: {
173: return Yaml::parseFile(config_path('auth.yaml'));
174: }
175: }