Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.48% covered (success)
84.48%
49 / 58
77.78% covered (success)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Login
84.48% covered (success)
84.48%
49 / 58
77.78% covered (success)
77.78%
7 / 9
32.14
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
84.62% covered (success)
84.62%
22 / 26
0.00% covered (danger)
0.00%
0 / 1
14.71
 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
143            if ( empty( $user ) || empty( $user->email ) ) {
144                return new WP_Error( 'google_login_failed', __( 'Failed to authenticate user.', 'login-with-google' ) );
145            }
146
147            /**
148             * Filter the restricted emails.
149             * Default to empty array.
150             *
151             * @param array $restricted_emails Array containing restricted emails.
152             */
153            $restricted_emails = apply_filters( 'rtcamp.restrict_user', array() );
154
155            if ( in_array( $user->email, $restricted_emails, true ) ) {
156                return new WP_Error( 'google_login_restricted', __( 'User restricted.', 'login-with-google' ) );
157            }
158
159            $user = $this->authenticator->authenticate( $user );
160
161            if ( $user instanceof WP_User ) {
162                $this->authenticated = true;
163
164                /**
165                 * Fires once the user has been authenticated via Google OAuth.
166                 *
167                 * @since 1.3.0
168                 *
169                 * @param WP_User $user WP User object.
170                 */
171                do_action( 'rtcamp.google_user_authenticated', $user );
172
173                return $user;
174            }
175
176            throw new Exception( __( 'Could not authenticate the user, please try again.', 'login-with-google' ) );
177
178        } catch ( Throwable $e ) {
179            return new WP_Error( 'google_login_failed', $e->getMessage() );
180        }
181    }
182
183    /**
184     * Add extra meta information about user.
185     *
186     * @param int $uid  User ID.
187     *
188     * @return void
189     */
190    public function user_meta( int $uid ) {
191        add_user_meta( $uid, 'oauth_user', 1, true );
192        add_user_meta( $uid, 'oauth_provider', 'google', true );
193    }
194
195    /**
196     * Redirect URL.
197     *
198     * This is useful when redirect URL is present when
199     * trying to login to wp-admin.
200     *
201     * @param string $url Redirect URL address.
202     *
203     * @return string
204     */
205    public function redirect_url( string $url ): string {
206
207        return remove_query_arg( 'redirect_to', $url );
208    }
209
210    /**
211     * Add redirect_to location in state.
212     *
213     * @param array $state State data.
214     *
215     * @return array
216     */
217    public function state_redirect( array $state ): array {
218        $redirect_to = Helper::filter_input( INPUT_GET, 'redirect_to', FILTER_SANITIZE_STRING );
219        /**
220         * Filter the default redirect URL in case redirect_to param is not available.
221         * Default to admin URL.
222         *
223         * @param string $admin_url Admin URL address.
224         */
225        $state['redirect_to'] = $redirect_to ?? apply_filters( 'rtcamp.google_default_redirect', admin_url() );
226
227        return $state;
228    }
229
230    /**
231     * Add a redirect once user has been authenticated successfully.
232     *
233     * @return void
234     */
235    public function login_redirect(): void {
236        $state = Helper::filter_input( INPUT_GET, 'state', FILTER_SANITIZE_STRING );
237
238        if ( ! $state || ! $this->authenticated ) {
239            return;
240        }
241
242        $state = base64_decode( $state );
243        $state = $state ? json_decode( $state ) : null;
244
245        if ( ( $state instanceof stdClass ) && ! empty( $state->provider ) && 'google' === $state->provider && ! empty( $state->redirect_to ) ) {
246            wp_safe_redirect( $state->redirect_to );
247            exit;
248        }
249    }
250}