-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathDiscourse.php
More file actions
150 lines (121 loc) · 3.88 KB
/
Discourse.php
File metadata and controls
150 lines (121 loc) · 3.88 KB
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
namespace SimpleSAML\Module\authdiscourse\Auth\Source;
use SimpleSAML\Auth;
use SimpleSAML\Configuration;
use SimpleSAML\Error;
use SimpleSAML\Logger;
use SimpleSAML\Module;
use SimpleSAML\Utils;
use Webmozart\Assert\Assert;
$base = dirname(dirname(dirname(dirname(__FILE__))));
/**
* Authenticate using Discourse.
*
* @package SimpleSAMLphp
*/
class Discourse extends Auth\Source
{
/**
* The string used to identify our states.
*/
public const STAGE_INIT = 'discourse:init';
/**
* The key of the AuthId field in the state.
*/
public const AUTHID = 'discourse:AuthId';
/**
* Discourse base URL
*
* @var string
*/
private $url;
/**
* Discourse SSO secret
* @var string
*/
private $secret;
/**
* Constructor for this authentication source.
*
* @param array $info Information about this authentication source.
* @param array $config Configuration.
*/
public function __construct(array $info, array $config)
{
// Call the parent constructor first, as required by the interface
parent::__construct($info, $config);
$configObject = Configuration::loadFromArray(
$config,
'authsources[' . var_export($this->authId, true) . ']'
);
$this->url = $configObject->getString('url');
$this->secret = $configObject->getString('secret');
}
/**
* Log-in using Discourse platform
*
* @param array &$state Information about the current authentication.
* @return void
*/
public function authenticate(array &$state): void
{
// We are going to need the authId in order to retrieve this authentication source later
$state[self::AUTHID] = $this->authId;
$nonce = hash('sha512', (string) mt_rand());
$state['authdiscourse:nonce'] = $nonce;
$stateID = Auth\State::saveState($state, self::STAGE_INIT);
$linkback = Module::getModuleURL('authdiscourse/linkback.php', ['AuthState' => $stateID]);
$payload = base64_encode(
http_build_query(
array (
'nonce' => $nonce,
'return_sso_url' => $linkback
)
)
);
$request = array(
'sso' => $payload,
'sig' => hash_hmac('sha256', $payload, $this->secret)
);
$query = http_build_query($request);
$discourseSsoUrl = $this->url . "/session/sso_provider?$query";
// Redirect user to Discourse SSO
Utils\HTTP::redirectTrustedURL($discourseSsoUrl);
}
/**
* @param array &$state
*/
public function finalStep(array &$state): void
{
$sso = (string) $_REQUEST['sso'];
$sig = (string) $_REQUEST['sig'];
if (!isset($sso)) {
throw new Error\BadRequest("Missing sso parameter from discourse.");
}
if (!isset($sig)) {
throw new Error\BadRequest("Missing sig parameter from discourse.");
}
// validate sso
if (hash_hmac('sha256', urldecode($sso), $this->secret) !== $sig) {
throw new Error\NotFound();
}
$sso = urldecode($sso);
$query = array();
parse_str(base64_decode($sso), $query);
// verify nonce with generated nonce during authenticate function
if ($query['nonce'] != $state['authdiscourse:nonce']) {
throw new Error\NotFound();
}
$attributes = [];
foreach ($query as $key => $value) {
if (is_string($value)) {
if ((string) $key == (string) 'groups') {
$attributes['discourse.groups'] = explode(",", $value);
} else {
$attributes['discourse.' . $key] = [(string) $value];
}
}
}
$state['Attributes'] = $attributes;
}
}