Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.79% covered (success)
86.79%
46 / 53
77.78% covered (success)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Login
86.79% covered (success)
86.79%
46 / 53
77.78% covered (success)
77.78%
7 / 9
27.56
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 init
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 login_button
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 authenticate
90.48% covered (success)
90.48%
19 / 21
0.00% covered (danger)
0.00%
0 / 1
11.10
 user_meta
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 redirect_url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 state_redirect
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 login_redirect
37.50% covered (warning)
37.50%
3 / 8
0.00% covered (danger)
0.00%
0 / 1
23.62
1<?php
2/**
3 * Login class.
4 *
5 * This will manage the login flow, which includes adding the
6 * google login button on wp-login page, authorizing the user,
7 * authenticating user and redirecting him to admin.
8 *
9 * @package RtCamp\GoogleLogin
10 * @since 1.0.0
11 */
12
13declare(strict_types=1);
14
15namespace RtCamp\GoogleLogin\Modules;
16
17use WP_User;
18use WP_Error;
19use stdClass;
20use Throwable;
21use Exception;
22use RtCamp\GoogleLogin\Utils\Helper;
23use RtCamp\GoogleLogin\Utils\GoogleClient;
24use RtCamp\GoogleLogin\Utils\Authenticator;
25use RtCamp\GoogleLogin\Interfaces\Module as ModuleInterface;
26use function RtCamp\GoogleLogin\plugin;
27
28/**
29 * Class Login.
30 *
31 * @package RtCamp\GoogleLogin\Modules
32 */
33class Login implements ModuleInterface {
34    /**
35     * Google client instance.
36     *
37     * @var GoogleClient
38     */
39    private $gh_client;
40
41    /**
42     * Authenticator instance.
43     *
44     * @var Authenticator
45     */
46    private $authenticator;
47
48    /**
49     * Flag for determining whether the user has been authenticated
50     * from plugin.
51     *
52     * @var bool
53     */
54    private $authenticated = false;
55
56    /**
57     * Login constructor.
58     *
59     * @param GoogleClient  $client GH Client object.
60     * @param Authenticator $authenticator Settings object.
61     */
62    public function __construct( GoogleClient $client, Authenticator $authenticator ) {
63        $this->gh_client     = $client;
64        $this->authenticator = $authenticator;
65    }
66
67    /**
68     * Module name.
69     *
70     * @return string
71     */
72    public function name(): string {
73        return 'login_flow';
74    }
75
76    /**
77     * Initialize login flow.
78     *
79     * @return void
80     */
81    public function init(): void {
82        add_action( 'login_form', [ $this, 'login_button' ] );
83        // Priority is 20 because of issue: https://core.trac.wordpress.org/ticket/46748.
84        add_action( 'authenticate', [ $this, 'authenticate' ], 20 );
85        add_action( 'rtcamp.google_register_user', [ $this->authenticator, 'register' ] );
86        add_action( 'rtcamp.google_redirect_url', [ $this, 'redirect_url' ] );
87        add_action( 'rtcamp.google_user_created', [ $this, 'user_meta' ] );
88        add_filter( 'rtcamp.google_login_state', [ $this, 'state_redirect' ] );
89        add_action( 'wp_login', [ $this, 'login_redirect' ] );
90    }
91
92    /**
93     * Add the login button to login form.
94     *
95     * @return void
96     */
97    public function login_button(): void {
98        $template  = trailingslashit( plugin()->template_dir ) . 'google-login-button.php';
99        $login_url = plugin()->container()->get( 'gh_client' )->authorization_url();
100
101        Helper::render_template(
102            $template,
103            [
104                'login_url' => $login_url,
105            ]
106        );
107    }
108
109    /**
110     * Authenticate the user.
111     *
112     * @param WP_User|null $user User object. Default is null.
113     *
114     * @return WP_User|WP_Error
115     * @throws Exception During authentication.
116     */
117    public function authenticate( $user = null ) {
118        if ( $user instanceof WP_User ) {
119            return $user;
120        }
121
122        $code = Helper::filter_input( INPUT_GET, 'code', FILTER_SANITIZE_STRING );
123
124        if ( ! $code ) {
125            return $user;
126        }
127
128        $state         = Helper::filter_input( INPUT_GET, 'state', FILTER_SANITIZE_STRING );
129        $decoded_state = $state ? (array) ( json_decode( base64_decode( $state ) ) ) : null;
130
131        if ( ! is_array( $decoded_state ) || empty( $decoded_state['provider'] ) || 'google' !== $decoded_state['provider'] ) {
132            return $user;
133        }
134
135        if ( empty( $decoded_state['nonce'] ) || ! wp_verify_nonce( $decoded_state['nonce'], 'login_with_google' ) ) {
136            return $user;
137        }
138
139        try {
140            $this->gh_client->set_access_token( $code );
141            $user = $this->gh_client->user();
142            $user = $this->authenticator->authenticate( $user );
143
144            if ( $user instanceof WP_User ) {
145                $this->authenticated = true;
146
147                /**
148                 * Fires once the user has been authenticated via Google OAuth.
149                 *
150                 * @since 1.3.0
151                 *
152                 * @param WP_User $user WP User object.
153                 */
154                do_action( 'rtcamp.google_user_authenticated', $user );
155
156                return $user;
157            }
158
159            throw new Exception( __( 'Could not authenticate the user, please try again.', 'login-with-google' ) );
160
161        } catch ( Throwable $e ) {
162            return new WP_Error( 'google_login_failed', $e->getMessage() );
163        }
164    }
165
166    /**
167     * Add extra meta information about user.
168     *
169     * @param int $uid  User ID.
170     *
171     * @return void
172     */
173    public function user_meta( int $uid ) {
174        add_user_meta( $uid, 'oauth_user', 1, true );
175        add_user_meta( $uid, 'oauth_provider', 'google', true );
176    }
177
178    /**
179     * Redirect URL.
180     *
181     * This is useful when redirect URL is present when
182     * trying to login to wp-admin.
183     *
184     * @param string $url Redirect URL address.
185     *
186     * @return string
187     */
188    public function redirect_url( string $url ): string {
189
190        return remove_query_arg( 'redirect_to', $url );
191    }
192
193    /**
194     * Add redirect_to location in state.
195     *
196     * @param array $state State data.
197     *
198     * @return array
199     */
200    public function state_redirect( array $state ): array {
201        $redirect_to = Helper::filter_input( INPUT_GET, 'redirect_to', FILTER_SANITIZE_STRING );
202        /**
203         * Filter the default redirect URL in case redirect_to param is not available.
204         * Default to admin URL.
205         *
206         * @param string $admin_url Admin URL address.
207         */
208        $state['redirect_to'] = $redirect_to ?? apply_filters( 'rtcamp.google_default_redirect', admin_url() );
209
210        return $state;
211    }
212
213    /**
214     * Add a redirect once user has been authenticated successfully.
215     *
216     * @return void
217     */
218    public function login_redirect(): void {
219        $state = Helper::filter_input( INPUT_GET, 'state', FILTER_SANITIZE_STRING );
220
221        if ( ! $state || ! $this->authenticated ) {
222            return;
223        }
224
225        $state = base64_decode( $state );
226        $state = $state ? json_decode( $state ) : null;
227
228        if ( ( $state instanceof stdClass ) && ! empty( $state->provider ) && 'google' === $state->provider && ! empty( $state->redirect_to ) ) {
229            wp_safe_redirect( $state->redirect_to );
230            exit;
231        }
232    }
233}