Completed
Push — master ( bc6e4f...594922 )
by Simon
01:39
created

JWTAuthenticator::authenticate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 3
1
<?php
2
3
namespace Firesphere\GraphQLJWT;
4
5
use BadMethodCallException;
6
use Lcobucci\JWT\Builder;
7
use Lcobucci\JWT\Parser;
8
use Lcobucci\JWT\Signer\Hmac\Sha256;
9
use Lcobucci\JWT\Token;
10
use SilverStripe\Control\Director;
11
use SilverStripe\Control\HTTPRequest;
12
use SilverStripe\Core\Config\Configurable;
13
use SilverStripe\GraphQL\Controller;
14
use SilverStripe\ORM\ValidationException;
15
use SilverStripe\ORM\ValidationResult;
16
use SilverStripe\Security\Authenticator;
17
use SilverStripe\Security\Member;
18
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
19
20
class JWTAuthenticator extends MemberAuthenticator
21
{
22
    use Configurable;
23
24
    /**
25
     * JWT is stateless, therefore, we don't support anything but login
26
     *
27
     * @return int
28
     */
29
    public function supportedServices()
30
    {
31
        return Authenticator::LOGIN | Authenticator::CMS_LOGIN;
32
    }
33
34
    /**
35
     * @param array $data
36
     * @param HTTPRequest $request
37
     * @param ValidationResult|null $result
38
     * @return Member|null
39
     * @throws \OutOfBoundsException
40
     * @throws \BadMethodCallException
41
     */
42
    public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null)
43
    {
44
        if (!$result) {
45
            $result = new ValidationResult();
46
        }
47
        $token = $data['token'];
48
49
        return $this->validateToken($token, $result);
50
    }
51
52
    /**
53
     * @param Member $member
54
     * @return Token
55
     * @throws ValidationException
56
     * @throws BadMethodCallException
57
     */
58
    public function generateToken(Member $member)
59
    {
60
        $config = static::config();
61
        $signer = new Sha256();
62
        $uniqueID = uniqid(getenv('JWT_PREFIX'), true);
63
64
        $request = Controller::curr()->getRequest();
65
        $audience = $request->getHeader('Origin');
66
        $signerKey = getenv('JWT_SIGNER_KEY');
67
68
        $builder = new Builder();
69
        $token = $builder
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setIssuer() has been deprecated with message: This method will be removed on v4, use issuedBy() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setAudience() has been deprecated with message: This method will be removed on v4, use canOnlyBeUsedBy() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
70
            // Configures the issuer (iss claim)
71
            ->setIssuer(Director::absoluteBaseURL())
0 ignored issues
show
Security Bug introduced by
It seems like \SilverStripe\Control\Director::absoluteBaseURL() targeting SilverStripe\Control\Director::absoluteBaseURL() can also be of type false; however, Lcobucci\JWT\Builder::setIssuer() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
72
            // Configures the audience (aud claim)
73
            ->setAudience($audience)
74
            // Configures the id (jti claim), replicating as a header item
75
            ->setId($uniqueID, true)
76
            // Configures the time that the token was issue (iat claim)
77
            ->setIssuedAt(time())
78
            // Configures the time that the token can be used (nbf claim)
79
            ->setNotBefore(time() + $config->get('nbf_time'))
80
            // Configures the expiration time of the token (nbf claim)
81
            ->setExpiration(time() + $config->get('nbf_expiration'))
82
            // Configures a new claim, called "uid"
83
            ->set('uid', $member->ID)
84
            // Sign the key with the Signer's key @todo: support certificates
85
            ->sign($signer, $signerKey);
86
87
        // Save the member if it's not anonymous
88
        if ($member->ID > 0) {
89
            $member->JWTUniqueID = $uniqueID;
90
            $member->write();
91
        }
92
93
        // Return the token
94
        return $token->getToken();
95
    }
96
97
    /**
98
     * @param string $token
99
     * @param ValidationResult $result
100
     * @return null|Member
101
     * @throws \OutOfBoundsException
102
     * @throws \BadMethodCallException
103
     */
104
    private function validateToken($token, &$result)
105
    {
106
        $parser = new Parser();
107
        $parsedToken = $parser->parse((string)$token);
108
        $signer = new Sha256();
109
        $signerKey = getenv('JWT_SIGNER_KEY');
110
        $member = null;
111
112
        // If the token is not verified, just give up
113
        if (!$parsedToken->verify($signer, $signerKey)) {
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Token::verify() has been deprecated with message: This method will be removed on v4, new validation API should be used

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
114
            $result->addError('Invalid token');
115
        }
116
        // An expired token can be renewed
117
        elseif ($parsedToken->isExpired()) {
118
            $result->addError('Token is expired, please renew your token with a refreshToken query');
119
        }
120
        // Everything seems fine, let's find a user
121
        elseif ($parsedToken->getClaim('uid') > 0 && $parsedToken->getClaim('jti')) {
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Token::getClaim() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
122
            /** @var Member $member */
123
            $member = Member::get()
124
                ->filter(['JWTUniqueID' => $parsedToken->getClaim('jti')])
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Token::getClaim() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
125
                ->byID($parsedToken->getClaim('uid'));
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Token::getClaim() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
126
        }
127
        // Not entirely fine, do we allow anonymous users?
128
        // Then, if the token is valid, return an anonymous user
129
        if (
130
            $result->isValid() &&
131
            $parsedToken->getClaim('uid') === 0 &&
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Token::getClaim() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
132
            static::config()->get('anonymous_allowed')
133
        ) {
134
            $member = Member::create(['ID' => 0, 'FirstName' => 'Anonymous']);
135
        }
136
137
        return $result->isValid() ? $member : null;
138
139
    }
140
}
141