Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.91% covered (success)
90.91%
30 / 33
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
URL_Secret
90.91% covered (success)
90.91%
30 / 33
75.00% covered (warning)
75.00%
6 / 8
17.22
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 fetch
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 create
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
 get_secret
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_expires_at
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 exists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 generate_secret
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create_secret
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
3.14
1<?php
2/**
3 * IDC URL secret functionality.
4 *
5 * @package  automattic/jetpack-connection
6 */
7
8namespace Automattic\Jetpack\IdentityCrisis;
9
10use Automattic\Jetpack\Connection\Urls;
11use Automattic\Jetpack\Tracking;
12use Jetpack_Options;
13
14/**
15 * IDC URL secret functionality.
16 * A short-lived secret used to verify whether an IDC is coming from the same vs a different Jetpack site.
17 */
18class URL_Secret {
19
20    /**
21     * The options key used to store the secret.
22     */
23    const OPTION_KEY = 'identity_crisis_url_secret';
24
25    /**
26     * Secret lifespan (5 minutes)
27     */
28    const LIFESPAN = 300;
29
30    /**
31     * The URL secret string.
32     *
33     * @var string|null
34     */
35    private $secret = null;
36
37    /**
38     * The URL secret expiration date in unix timestamp.
39     *
40     * @var string|null
41     */
42    private $expires_at = null;
43
44    /**
45     * Initialize the class.
46     */
47    public function __construct() {
48        $secret_data = $this->fetch();
49
50        if ( $secret_data !== null ) {
51            $this->secret     = $secret_data['secret'];
52            $this->expires_at = $secret_data['expires_at'];
53        }
54    }
55
56    /**
57     * Fetch the URL secret from the database.
58     *
59     * @return array|null
60     */
61    private function fetch() {
62        $data = Jetpack_Options::get_option( static::OPTION_KEY );
63
64        if ( $data === false || empty( $data['secret'] ) || empty( $data['expires_at'] ) ) {
65            return null;
66        }
67
68        if ( time() > $data['expires_at'] ) {
69            Jetpack_Options::delete_option( static::OPTION_KEY );
70            return null;
71        }
72
73        return $data;
74    }
75
76    /**
77     * Create new secret and save it in the options.
78     *
79     * @throws Exception Thrown if unable to save the new secret.
80     *
81     * @return bool
82     */
83    public function create() {
84        $secret_data = array(
85            'secret'     => $this->generate_secret(),
86            'expires_at' => strval( time() + static::LIFESPAN ),
87        );
88
89        $result = Jetpack_Options::update_option( static::OPTION_KEY, $secret_data );
90
91        if ( ! $result ) {
92            throw new Exception( esc_html__( 'Unable to save new URL secret', 'jetpack-connection' ) );
93        }
94
95        $this->secret     = $secret_data['secret'];
96        $this->expires_at = $secret_data['expires_at'];
97
98        return true;
99    }
100
101    /**
102     * Get the URL secret.
103     *
104     * @return string|null
105     */
106    public function get_secret() {
107        return $this->secret;
108    }
109
110    /**
111     * Get the URL secret expiration date.
112     *
113     * @return string|null
114     */
115    public function get_expires_at() {
116        return $this->expires_at;
117    }
118
119    /**
120     * Check if the secret exists.
121     *
122     * @return bool
123     */
124    public function exists() {
125        return $this->secret && $this->expires_at;
126    }
127
128    /**
129     * Generate the secret string.
130     *
131     * @return string
132     */
133    private function generate_secret() {
134        return wp_generate_password( 12, false );
135    }
136
137    /**
138     * Generate secret for response.
139     *
140     * @param string $flow used to tell which flow generated the exception.
141     * @return string|null
142     */
143    public static function create_secret( $flow = 'generating_secret_failed' ) {
144        $secret_value = null;
145        try {
146
147            $secret = new self();
148            $secret->create();
149
150            if ( $secret->exists() ) {
151                $secret_value = $secret->get_secret();
152            }
153        } catch ( Exception $e ) {
154            // Track the error and proceed.
155            ( new Tracking() )->record_user_event( $flow, array( 'current_url' => Urls::site_url() ) );
156        }
157        return $secret_value;
158    }
159}