summaryrefslogtreecommitdiffstats
path: root/modules-available/sysconfig/inc/shib.inc.php
blob: c39565720b4016aa2c158709b9d7e0b6d477bb9b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php

class Shib
{
	const SHIB_LOOKUP_JSON = '/var/cache/slx-admin/idp2suffix.json';

	const SHIB_DISCO_JSON = '/var/cache/slx-admin/discofeed.json';

	const RET_ERR = 0;
	const RET_UPDATE = 1;
	const RET_UNCHANGED = 2;

	/**
	 * Updates the local copy of the JSON file for mapping IDP entityIDs to scope suffixes.
	 * If the list changed, all ShibAuth modules will be rebuilt.
	 *
	 * @return bool true on success, or if list didn't change. false otherwise.
	 */
	public static function refreshIdpSuffixMap(): bool
	{
		$changed = false;
		$ret = self::updateFromUrl(CONFIG_SHIB_LOOKUP_URL . '?what=id2suffix', self::SHIB_LOOKUP_JSON);
		if ($ret === self::RET_ERR)
			return false;
		if ($ret === self::RET_UPDATE) {
			$changed = true;
		}
		$ret = self::updateFromUrl(CONFIG_SHIB_DISCO_URL, self::SHIB_DISCO_JSON);
		if ($ret === self::RET_ERR)
			return false;
		if ($ret === self::RET_UPDATE) {
			$changed = true;
		}
		if ($changed) {
			// Rebuild any modules in the background
			$configs = [];
			$list = ConfigModule::getAll('ShibAuth');
			$parent = null;
			foreach ($list as $mod) {
				$r = $mod->generate(false, $parent);
				if (is_string($r)) {
					$parent = $r;
				}
				foreach (ConfigTgz::getAllForModule($mod->id()) as $tgz) {
					$configs[$tgz->id()] = $tgz;
				}
			}
			foreach ($configs as $tgz) {
				$r = $tgz->generate(false, 0, $parent);
				if (is_string($r)) {
					$parent = $r;
				}
			}
		}
		return $ret;
	}

	private static function updateFromUrl(string $url, string $file): int
	{
		if (!file_exists($file) || filemtime($file) + 86400 < time()) {
			$code = null;
			$ret = Download::toFile($file . '.tmp',
				$url, 15, $code);
			if (!$ret)
				return self::RET_ERR;
			if (!file_exists($file)
				|| filesize($file) !== filesize($file . '.tmp')
				|| sha1_file($file, true) !== sha1_file($file . '.tmp', true)
			) {
				rename($file . '.tmp', $file);
				if ($code >= 200 && $code < 300)
					return self::RET_UPDATE;
				return self::RET_ERR; // Not 2xx range, keep file but return error
			}
			unlink($file . '.tmp');
		}
		return self::RET_UNCHANGED;
	}

	/**
	 * @return ?array of registrars (key) containing array{"registrar": string, "list": string[]}
	 */
	public static function getListByRegistrar(): ?array
	{
		$disco = json_decode(file_get_contents(self::SHIB_DISCO_JSON), true);
		$lookup = json_decode(file_get_contents(self::SHIB_LOOKUP_JSON), true);
		if (!is_array($disco) || !is_array($lookup)) {
			return null;
		}
		foreach ($disco as $item) {
			if (!isset($item['entityID'])) {
				continue;
			}
			$id = $item['entityID'];
			$name = null;
			$fallback = null;
			foreach ($item['DisplayNames'] ?? [] as $dn) {
				if ($dn['lang'] === LANG) {
					$name = $dn['value'];
				} elseif ($name === null && $dn['lang'] === 'en') {
					$name = $dn['value'];
				} elseif ($fallback === null) {
					$fallback = $dn['value'];
				}
			}
			if ($name === null) {
				$name = $fallback;
			}
			$registrar = $lookup[$id]['regauth'] ?? 'unknown';
			if (!isset($return[$registrar])) {
				$return[$registrar] = ['list' => [], 'registrar' => $registrar];
			}
			$return[$registrar]['list'][] = [
				'id' => $id,
				'name' => $name ?? $id,
			];
		}
		return $return;
	}

	public static function getIdp2SuffixList(): ?array
	{
		$lookup = json_decode(file_get_contents(self::SHIB_LOOKUP_JSON), true);
		if (!is_array($lookup))
			return null;
		return $lookup;
	}

	/**
	 * Map given list of registrar IDs back to all the IdP entityIDs they cover.
	 * @param string[] $regs
	 * @return string[]
	 */
	public static function explodeRegistrars(array $regs): array
	{
		if (empty($regs))
			return [];
		$idps = [];
		$reg2idps = Shib::getListByRegistrar();
		foreach ($regs as $reg) {
			if (!isset($reg2idps[$reg]['list']))
				continue;
			foreach ($reg2idps[$reg]['list'] as $elem) {
				$idps[] = $elem['id'];
			}
		}
		return $idps;
	}

}