Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.42% covered (success)
85.42%
41 / 48
77.78% covered (success)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Login
85.42% covered (success)
85.42%
41 / 48
77.78% covered (success)
77.78%
7 / 9
28.10
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%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 authenticate
90.00% covered (success)
90.00%
18 / 20
0.00% covered (danger)
0.00%
0 / 1
11.12
 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        add_action( 'authenticate', [ $this, 'authenticate' ] );
84        add_action( 'rtcamp.google_register_user', [ $this->authenticator, 'register' ] );
85        add_action( 'rtcamp.google_redirect_url', [ $this, 'redirect_url' ] );
86        add_action( 'rtcamp.google_user_created', [ $this, 'user_meta' ] );
87        add_filter( 'rtcamp.google_login_state', [ $this, 'state_redirect' ] );
88        add_action( 'wp_login', [ $this, 'login_redirect' ] );
89    }
90
91    /**
92     * Add the login button to login form.
93     *
94     * @return void
95     */
96    public function login_button(): void {
97        $template  = trailingslashit( plugin()->template_dir ) . 'google-login-button.php';
98        $login_url = plugin()->container()->get( 'gh_client' )->authorization_url();
99
100        Helper::render_template(
101            $template,
102            [
103                'login_url' => $login_url,
104            ]
105        );
106    }
107
108    /**
109     * Authenticate the user.
110     *
111     * @param WP_User|null $user User object. Default is null.
112     *
113     * @return WP_User|WP_Error
114     * @throws Exception During authentication.
115     */
116    public function authenticate( $user = null ) {
117        if ( $user instanceof WP_User ) {
118            return $user;
119        }
120
121        $code = Helper::filter_input( INPUT_GET, 'code' );
122
123        if ( ! $code ) {
124            return $user;
125        }
126
127        $state         = Helper::filter_input( INPUT_GET, 'state' );
128        $decoded_state = $state ? (array) ( json_decode( base64_decode( $state ) ) ) : null;
129
130        if ( ! is_array( $decoded_state ) || empty( $decoded_state['provider'] ) || 'google' !== $decoded_state['provider'] ) {
131            return $user;
132        }
133
134        if ( empty( $decoded_state['nonce'] ) || ! wp_verify_nonce( $decoded_state['nonce'], 'login_with_google' ) ) {
135            return $user;
136        }
137
138        try {
139            $this->gh_client->set_access_token( $code );
140            $user = $this->gh_client->user();
141            $user = $this->authenticator->authenticate( $user );
142
143            if ( $user instanceof WP_User ) {
144                $this->authenticated = true;
145
146                return $user;
147            }
148
149            throw new Exception( __( 'Could not authenticate the user, please try again.', 'login-with-google' ) );
150
151        } catch ( Throwable $e ) {
152            return new WP_Error( 'google_login_failed', $e->getMessage() );
153        }
154    }
155
156    /**
157     * Add extra meta information about user.
158     *
159     * @param int $uid  User ID.
160     *
161     * @return void
162     */
163    public function user_meta( int $uid ) {
164        add_user_meta( $uid, 'oauth_user', 1, true );
165        add_user_meta( $uid, 'oauth_provider', 'google', true );
166    }
167
168    /**
169     * Redirect URL.
170     *
171     * This is useful when redirect URL is present when
172     * trying to login to wp-admin.
173     *
174     * @param string $url Redirect URL address.
175     *
176     * @return string
177     */
178    public function redirect_url( string $url ): string {
179
180        return remove_query_arg( 'redirect_to', $url );
181    }
182
183    /**
184     * Add redirect_to location in state.
185     *
186     * @param array $state State data.
187     *
188     * @return array
189     */
190    public function state_redirect( array $state ): array {
191        $redirect_to = Helper::filter_input( INPUT_GET, 'redirect_to' );
192        /**
193         * Filter the default redirect URL in case redirect_to param is not available.
194         * Default to admin URL.
195         *
196         * @param string $admin_url Admin URL address.
197         */
198        $state['redirect_to'] = $redirect_to ?? apply_filters( 'rtcamp.google_default_redirect', admin_url() );
199
200        return $state;
201    }
202
203    /**
204     * Add a redirect once user has been authenticated successfully.
205     *
206     * @return void
207     */
208    public function login_redirect(): void {
209        $state = Helper::filter_input( INPUT_GET, 'state' );
210
211        if ( ! $state || ! $this->authenticated ) {
212            return;
213        }
214
215        $state = base64_decode( $state );
216        $state = $state ? json_decode( $state ) : null;
217
218        if ( ( $state instanceof stdClass ) && ! empty( $state->provider ) && 'google' === $state->provider && ! empty( $state->redirect_to ) ) {
219            wp_safe_redirect( $state->redirect_to );
220            exit;
221        }
222    }
223}