Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace Aws\Token;
3
 
4
use Aws\Exception\TokenException;
5
use GuzzleHttp\Promise;
6
 
7
/**
8
 * Token that comes from the SSO provider
9
 */
10
class SsoTokenProvider implements RefreshableTokenProviderInterface
11
{
12
    use ParsesIniTrait;
13
 
14
    const ENV_PROFILE = 'AWS_PROFILE';
15
 
16
    private $ssoProfileName;
17
    private $filename;
18
    private $ssoOidcClient;
19
 
20
    /**
21
     * Constructs a new SsoTokenProvider object, which will fetch a token from an authenticated SSO profile
22
     * @param string $ssoProfileName The name of the profile that contains the sso_session key
23
     * @param int    $filename Name of the config file to sso profile from
24
     */
25
    public function __construct($ssoProfileName, $filename = null, $ssoOidcClient = null) {
26
        $profileName = getenv(self::ENV_PROFILE) ?: 'default';
27
        $this->ssoProfileName = !empty($ssoProfileName) ? $ssoProfileName : $profileName;
28
        $this->filename =  !empty($filename)
29
            ? $filename :
30
            self::getHomeDir() . '/.aws/config';
31
        $this->ssoOidcClient = $ssoOidcClient;
32
    }
33
 
34
    /*
35
     * Loads cached sso credentials
36
     *
37
     * @return PromiseInterface
38
     */
39
    public function __invoke()
40
    {
41
        return Promise\Coroutine::of(function () {
42
            if (!@is_readable($this->filename)) {
43
                throw new TokenException("Cannot read profiles from $this->filename");
44
            }
45
            $profiles = self::loadProfiles($this->filename);
46
            if (!isset($profiles[$this->ssoProfileName])) {
47
                throw new TokenException("Profile {$this->ssoProfileName} does not exist in {$this->filename}.");
48
            }
49
            $ssoProfile = $profiles[$this->ssoProfileName];
50
            if (empty($ssoProfile['sso_session'])) {
51
                throw new TokenException(
52
                    "Profile {$this->ssoProfileName} in {$this->filename} must contain an sso_session."
53
                );
54
            }
55
 
56
            $sessionProfileName = 'sso-session ' . $ssoProfile['sso_session'];
57
            if (empty($profiles[$sessionProfileName])) {
58
                throw new TokenException(
59
                    "Profile {$this->ssoProfileName} does not exist in {$this->filename}"
60
                );
61
            }
62
 
63
            $sessionProfileData = $profiles[$sessionProfileName];
64
            if (empty($sessionProfileData['sso_start_url'])
65
                || empty($sessionProfileData['sso_region'])
66
            ) {
67
                throw new TokenException(
68
                    "Profile {$this->ssoProfileName} in {$this->filename} must contain the following keys: "
69
                    . "sso_start_url and sso_region."
70
                );
71
            }
72
 
73
            $tokenLocation = self::getTokenLocation($ssoProfile['sso_session']);
74
            if (!@is_readable($tokenLocation)) {
75
                throw new TokenException("Unable to read token file at $tokenLocation");
76
            }
77
            $tokenData = $this->getTokenData($tokenLocation);
78
            $this->validateTokenData($tokenLocation, $tokenData);
79
            yield new SsoToken(
80
                $tokenData['accessToken'],
81
                $tokenData['expiresAt'],
82
                isset($tokenData['refreshToken']) ? $tokenData['refreshToken'] : null,
83
                isset($tokenData['clientId']) ? $tokenData['clientId'] : null,
84
                isset($tokenData['clientSecret']) ? $tokenData['clientSecret'] : null,
85
                isset($tokenData['registrationExpiresAt']) ? $tokenData['registrationExpiresAt'] : null,
86
                isset($tokenData['region']) ? $tokenData['region'] : null,
87
                isset($tokenData['startUrl']) ? $tokenData['startUrl'] : null
88
            );
89
        });
90
    }
91
 
92
    /**
93
     * Refreshes the token
94
     * @return mixed|null
95
     */
96
    public function refresh() {
97
        try {
98
            //try to reload from disk
99
            $token = $this();
100
            if (
101
                $token instanceof SsoToken
102
                && !$token->shouldAttemptRefresh()
103
            ) {
104
                return $token;
105
            }
106
        } finally {
107
            //if reload from disk fails, try refreshing
108
            $tokenLocation = self::getTokenLocation($this->ssoProfileName);
109
            $tokenData = $this->getTokenData($tokenLocation);
110
            if (
111
                empty($this->ssoOidcClient)
112
                || empty($tokenData['startUrl'])
113
            ) {
114
                throw new TokenException(
115
                    "Cannot refresh this token without an 'ssooidcClient' "
116
                    . "and a 'start_url'"
117
                );
118
            }
119
            $response = $this->ssoOidcClient->createToken([
120
                'clientId' => $tokenData['clientId'],
121
                'clientSecret' => $tokenData['clientSecret'],
122
                'grantType' => 'refresh_token', // REQUIRED
123
                'refreshToken' => $tokenData['refreshToken'],
124
            ]);
125
            if ($response['@metadata']['statusCode'] == 200) {
126
                $tokenData['accessToken'] = $response['accessToken'];
127
                $tokenData['expiresAt'] = time () + $response['expiresIn'];
128
                $tokenData['refreshToken'] = $response['refreshToken'];
129
                $token = new SsoToken(
130
                    $tokenData['accessToken'],
131
                    $tokenData['expiresAt'],
132
                    $tokenData['refreshToken'],
133
                    isset($tokenData['clientId']) ? $tokenData['clientId'] : null,
134
                    isset($tokenData['clientSecret']) ? $tokenData['clientSecret'] : null,
135
                    isset($tokenData['registrationExpiresAt']) ? $tokenData['registrationExpiresAt'] : null,
136
                    isset($tokenData['region']) ? $tokenData['region'] : null,
137
                    isset($tokenData['startUrl']) ? $tokenData['startUrl'] : null                );
138
 
139
                $this->writeNewTokenDataToDisk($tokenData, $tokenLocation);
140
 
141
                return $token;
142
            }
143
        }
144
    }
145
 
146
    public function shouldAttemptRefresh()
147
    {
148
        $tokenLocation = self::getTokenLocation($this->ssoProfileName);
149
        $tokenData = $this->getTokenData($tokenLocation);
150
        return strtotime("-10 minutes") >= strtotime($tokenData['expiresAt']);
151
    }
152
 
153
    /**
154
     * @param $sso_session
155
     * @return string
156
     */
157
    public static function getTokenLocation($sso_session)
158
    {
159
        return self::getHomeDir()
160
            . '/.aws/sso/cache/'
161
            . mb_convert_encoding(sha1($sso_session), "UTF-8")
162
            . ".json";
163
    }
164
 
165
    /**
166
     * @param $tokenLocation
167
     * @return array
168
     */
169
    function getTokenData($tokenLocation)
170
    {
171
        return json_decode(file_get_contents($tokenLocation), true);
172
    }
173
 
174
    /**
175
     * @param $tokenData
176
     * @param $tokenLocation
177
     * @return mixed
178
     */
179
    private function validateTokenData($tokenLocation, $tokenData)
180
    {
181
        if (empty($tokenData['accessToken']) || empty($tokenData['expiresAt'])) {
182
            throw new TokenException(
183
                "Token file at {$tokenLocation} must contain an access token and an expiration"
184
            );
185
        }
186
 
187
        $expiration = strtotime($tokenData['expiresAt']);
188
        if ($expiration === false) {
189
            throw new TokenException("Cached SSO token returned an invalid expiration");
190
        } elseif ($expiration < time()) {
191
            throw new TokenException("Cached SSO token returned an expired token");
192
        }
193
        return $tokenData;
194
    }
195
 
196
    /**
197
     * @param array $tokenData
198
     * @param string $tokenLocation
199
     * @return void
200
     */
201
    private function writeNewTokenDataToDisk(array $tokenData, $tokenLocation)
202
    {
203
        $tokenData['expiresAt'] = gmdate(
204
            'Y-m-d\TH:i:s\Z',
205
            $tokenData['expiresAt']
206
        );
207
        file_put_contents($tokenLocation, json_encode(array_filter($tokenData)));
208
    }
209
}