Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
83.33% covered (success)
83.33%
10 / 12
CRAP
90.82% covered (success)
90.82%
89 / 98
Login
0.00% covered (danger)
0.00%
0 / 1
83.33% covered (success)
83.33%
10 / 12
37.53
92.71% covered (success)
92.71%
89 / 96
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 name
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 init
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
10 / 10
 login_button
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 authenticate
0.00% covered (danger)
0.00%
0 / 1
11
95.24% covered (success)
95.24%
20 / 21
 register
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
17 / 17
 user_meta
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 redirect_url
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 state_redirect
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 login_redirect
0.00% covered (danger)
0.00%
0 / 1
21.82
40.00% covered (warning)
40.00%
4 / 10
 user_login
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
8 / 8
 can_register_with_email
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
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 stdClass;
18use RtCamp\GoogleLogin\Utils\GoogleClient;
19use RtCamp\GoogleLogin\Utils\Helper;
20use WP_User;
21use WP_Error;
22use Exception;
23use RtCamp\GoogleLogin\Interfaces\Module as ModuleInterface;
24use function RtCamp\GoogleLogin\plugin;
25
26/**
27 * Class Login.
28 *
29 * @package RtCamp\GoogleLogin\Modules
30 */
31class Login implements ModuleInterface {
32    /**
33     * Google client instance.
34     *
35     * @var GoogleClient
36     */
37    private $gh_client;
38
39    /**
40     * Settings object.
41     *
42     * @var Settings
43     */
44    private $settings;
45
46    /**
47     * Flag for determining whether the user has been authenticated
48     * from plugin.
49     *
50     * @var bool
51     */
52    private $authenticated = false;
53
54    /**
55     * Login constructor.
56     *
57     * @param GoogleClient $client GH Client object.
58     * @param Settings     $settings Settings object.
59     */
60    public function __construct( GoogleClient $client, Settings $settings ) {
61        $this->gh_client = $client;
62        $this->settings  = $settings;
63    }
64
65    /**
66     * Module name.
67     *
68     * @return string
69     */
70    public function name(): string {
71        return 'login_flow';
72    }
73
74    /**
75     * Initialize login flow.
76     *
77     * @return void
78     */
79    public function init(): void {
80        add_action( 'login_form', [ $this, 'login_button' ] );
81        add_action( 'authenticate', [ $this, 'authenticate' ] );
82        add_action( 'rtcamp.google_register_user', [ $this, 'register' ] );
83        add_action( 'rtcamp.google_redirect_url', [ $this, 'redirect_url' ] );
84        add_action( 'rtcamp.google_user_created', [ $this, 'user_meta' ], 10, 2 );
85        add_filter( 'rtcamp.google_user_profile', [ $this, 'user_login' ] );
86        add_filter( 'rtcamp.google_login_state', [ $this, 'state_redirect' ] );
87        add_action( 'wp_login', [ $this, 'login_redirect' ] );
88    }
89
90    /**
91     * Add the login button to login form.
92     *
93     * @return void
94     */
95    public function login_button(): void {
96        $template  = trailingslashit( plugin()->template_dir ) . 'google-login-button.php';
97        $login_url = plugin()->container()->get( 'gh_client' )->authorization_url();
98
99        Helper::render_template(
100            $template,
101            [
102                'login_url' => $login_url,
103            ]
104        );
105    }
106
107    /**
108     * Authenticate the user.
109     *
110     * @param WP_User|null $user User object. Default is null.
111     *
112     * @return WP_User|WP_Error
113     */
114    public function authenticate( $user = null ) {
115        if ( $user instanceof WP_User ) {
116            return $user;
117        }
118
119        $code = Helper::filter_input( INPUT_GET, 'code', FILTER_SANITIZE_STRING );
120
121        if ( ! $code ) {
122            return $user;
123        }
124
125        $state          = Helper::filter_input( INPUT_GET, 'state', FILTER_SANITIZE_STRING );
126        $decoded_state  = $state ? (array) ( json_decode( base64_decode( $state ) ) ) : null;
127
128        if ( ! is_array( $decoded_state ) || empty( $decoded_state['provider'] ) || 'google' !== $decoded_state['provider'] ) {
129            return $user;
130        }
131
132        if ( empty( $decoded_state['nonce'] ) || ! wp_verify_nonce( $decoded_state['nonce'], 'login_with_google' ) ) {
133            return $user;
134        }
135
136        try {
137            $this->gh_client->set_access_token( $code );
138            $user = $this->gh_client->user();
139            $user = apply_filters( 'rtcamp.google_user_profile', $user );
140
141            if ( email_exists( $user->email ) ) {
142                $this->authenticated = true;
143                return get_user_by( 'email', $user->email );
144            }
145
146            /**
147             * Check if we need to register the user.
148             *
149             * @param stdClass $user User object from google.
150             * @since 1.0.0
151             */
152            return apply_filters( 'rtcamp.google_register_user', $user );
153
154        } catch ( \Throwable $e ) {
155            return new WP_Error( 'google_login_failed', $e->getMessage() );
156        }
157    }
158
159    /**
160     * Register the new user if setting is on for registration.
161     *
162     * @param stdClass $user User object from google.
163     *
164     * @return WP_User|null
165     * @throws \Throwable Invalid email registration.
166     * @throws Exception Registration is off.
167     */
168    public function register( stdClass $user ): ?WP_User {
169        $register = true === (bool) $this->settings->registration_enabled || (bool) get_option( 'users_can_register', false );
170
171        if ( ! $register ) {
172            throw new Exception( __( 'Registration is not allowed.', 'login-with-google' ) );
173        }
174
175        try {
176            $whitelisted_domains = $this->settings->whitelisted_domains;
177            if ( empty( $whitelisted_domains ) || $this->can_register_with_email( $user->email ) ) {
178                $uid = wp_insert_user(
179                    [
180                        'user_login' => Helper::unique_username( $user->login ),
181                        'user_pass'  => wp_generate_password( 18 ),
182                        'user_email' => $user->email,
183                    ]
184                );
185
186                if ( $uid ) {
187                    $this->authenticated = true;
188                }
189
190                /**
191                 * Fires once the user has been registered successfully.
192                 */
193                do_action( 'rtcamp.google_user_created', $uid, $user );
194
195                return get_user_by( 'id', $uid );
196            }
197
198            /* translators: %s is replaced with email ID of user trying to register */
199            throw new Exception( sprintf( __( 'Cannot register with this email: %s', 'login-with-google' ), $user->email ) );
200
201        } catch ( \Throwable $e ) {
202
203            throw $e;
204        }
205
206    }
207
208    /**
209     * Add extra meta information about user.
210     *
211     * @param int      $uid  User ID.
212     * @param stdClass $user User object.
213     *
214     * @return void
215     */
216    public function user_meta( int $uid, stdClass $user ) {
217        add_user_meta( $uid, 'oauth_user', 1, true );
218        add_user_meta( $uid, 'oauth_provider', 'google', true );
219    }
220
221    /**
222     * Redirect URL.
223     *
224     * This is useful when redirect URL is present when
225     * trying to login to wp-admin.
226     *
227     * @param string $url Redirect URL address.
228     *
229     * @return string
230     */
231    public function redirect_url( string $url ): string {
232
233        return remove_query_arg( 'redirect_to', $url );
234    }
235
236    /**
237     * Add redirect_to location in state.
238     *
239     * @param array $state State data.
240     *
241     * @return array
242     */
243    public function state_redirect( array $state ): array {
244        $redirect_to          = Helper::filter_input( INPUT_GET, 'redirect_to', FILTER_SANITIZE_STRING );
245        /**
246         * Filter the default redirect URL in case redirect_to param is not available.
247         * Default to admin URL.
248         *
249         * @param string $admin_url Admin URL address.
250         */
251        $state['redirect_to'] = $redirect_to ?? apply_filters( 'rtcamp.google_default_redirect', admin_url() );
252
253        return $state;
254    }
255
256    /**
257     * Add a redirect once user has been authenticated successfully.
258     *
259     * @return void
260     */
261    public function login_redirect(): void {
262        $state = Helper::filter_input( INPUT_GET, 'state', FILTER_SANITIZE_STRING );
263
264        if ( ! $state || ! $this->authenticated ) {
265            return;
266        }
267
268        $state = base64_decode( $state );
269        $state = $state ? json_decode( $state ) : null;
270
271        if ( ( $state instanceof stdClass ) && ! empty( $state->provider ) && 'google' === $state->provider && ! empty ( $state->redirect_to ) ) {
272            wp_safe_redirect( $state->redirect_to );
273            exit;
274        }
275    }
276
277    /**
278     * Assign the `login` property to user object
279     * if it doesn't exists.
280     *
281     * @param stdClass $user User object.
282     *
283     * @return stdClass
284     */
285    public function user_login( stdClass $user ): stdClass {
286        if ( property_exists( $user, 'login' ) || ! property_exists( $user, 'email' ) ) {
287            return $user;
288        }
289
290        $email       = $user->email;
291        $user_login  = sanitize_user( current( explode( '@', $email ) ), true );
292        $user_login  = Helper::unique_username( $user_login );
293        $user->login = $user_login;
294
295        return $user;
296    }
297
298    /**
299     * Check if given email can be used for registration.
300     *
301     * @param string $email Email ID.
302     *
303     * @return bool
304     */
305    private function can_register_with_email( string $email ): bool {
306        $whitelisted_domains = explode( ',', $this->settings->whitelisted_domains );
307        $whitelisted_domains = array_map( 'strtolower', $whitelisted_domains );
308        $whitelisted_domains = array_map( 'trim', $whitelisted_domains );
309        $email_parts         = explode( '@', $email );
310        $email_parts         = array_map( 'strtolower', $email_parts );
311
312        return in_array( $email_parts[1], $whitelisted_domains, true );
313    }
314}