Completed
Pull Request — master (#196)
by Robbie
13:18
created

SecurityToken::checkRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 1
1
<?php
2
3
namespace SilverStripe\Comments\Model\Comment;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Security\Member;
7
use SilverStripe\Security\RandomGenerator;
8
9
/**
10
 * Provides the ability to generate cryptographically secure tokens for comment moderation
11
 */
12
class SecurityToken
13
{
14
    /**
15
     * @var string
16
     */
17
    private $secret = null;
18
19
    /**
20
     * @param Comment $comment Comment to generate this token for
21
     */
22
    public function __construct($comment)
23
    {
24
        if (!$comment->SecretToken) {
25
            $comment->SecretToken = $this->generate();
26
            $comment->write();
27
        }
28
        $this->secret = $comment->SecretToken;
29
    }
30
31
    /**
32
     * Generate the token for the given salt and current secret
33
     *
34
     * @param string $salt
35
     *
36
     * @return string
37
     */
38
    protected function getToken($salt)
39
    {
40
        return hash_pbkdf2('sha256', $this->secret, $salt, 1000, 30);
41
    }
42
43
    /**
44
     * Get the member-specific salt.
45
     *
46
     * The reason for making the salt specific to a user is that it cannot be "passed in" via a
47
     * querystring, requiring the same user to be present at both the link generation and the
48
     * controller action.
49
     *
50
     * @param string $salt   Single use salt
51
     * @param Member $member Member object
52
     *
53
     * @return string Generated salt specific to this member
54
     */
55
    protected function memberSalt($salt, $member)
56
    {
57
        // Fallback to salting with ID in case the member has not one set
58
        return $salt . ($member->Salt ?: $member->ID);
59
    }
60
61
    /**
62
     * @param string $url    Comment action URL
63
     * @param Member $member Member to restrict access to this action to
64
     *
65
     * @return string
66
     */
67
    public function addToUrl($url, $member)
68
    {
69
        $salt = $this->generate(15); // New random salt; Will be passed into url
70
        // Generate salt specific to this member
71
        $memberSalt = $this->memberSalt($salt, $member);
72
        $token = $this->getToken($memberSalt);
73
        return Controller::join_links(
74
            $url,
75
            sprintf(
76
                '?t=%s&s=%s',
77
                urlencode($token),
78
                urlencode($salt)
79
            )
80
        );
81
    }
82
83
    /**
84
     * @param SS_HTTPRequest $request
85
     *
86
     * @return boolean
87
     */
88
    public function checkRequest($request)
89
    {
90
        $member = Member::currentUser();
91
        if (!$member) {
92
            return false;
93
        }
94
95
        $salt = $request->getVar('s');
96
        $memberSalt = $this->memberSalt($salt, $member);
97
        $token = $this->getToken($memberSalt);
98
99
        // Ensure tokens match
100
        return $token === $request->getVar('t');
101
    }
102
103
104
    /**
105
     * Generates new random key
106
     *
107
     * @param integer $length
0 ignored issues
show
Documentation introduced by
Should the type for parameter $length not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
108
     *
109
     * @return string
110
     */
111
    protected function generate($length = null)
112
    {
113
        $generator = new RandomGenerator();
114
        $result = $generator->randomToken('sha256');
115
        if ($length !== null) {
116
            return substr($result, 0, $length);
117
        }
118
        return $result;
119
    }
120
}
121