Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 71
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
OneTapLogin
0.00% covered (danger)
0.00%
0 / 71
0.00% covered (danger)
0.00%
0 / 7
380
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 init
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 one_tap_prompt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 one_tap_scripts
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
12
 validate_token
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
56
 authenticate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * One Tap Login Class.
4 *
5 * This class will be responsible for handling
6 * Google's one tap login for web functioning.
7 *
8 * @package RtCamp\GoogleLogin\Modules
9 * @since 1.0.16
10 */
11
12declare(strict_types=1);
13
14namespace RtCamp\GoogleLogin\Modules;
15
16use Exception;
17use RtCamp\GoogleLogin\Utils\Authenticator;
18use RtCamp\GoogleLogin\Utils\GoogleClient;
19use RtCamp\GoogleLogin\Utils\Helper;
20use RtCamp\GoogleLogin\Interfaces\Module;
21use RtCamp\GoogleLogin\Utils\TokenVerifier;
22use function RtCamp\GoogleLogin\plugin;
23
24/**
25 * Class OneTapLogin
26 *
27 * @package RtCamp\GoogleLogin\Modules
28 */
29class OneTapLogin implements Module {
30    /**
31     * Settings Module.
32     *
33     * @var Settings
34     */
35    private $settings;
36
37    /**
38     * Token verifier.
39     *
40     * @var TokenVerifier
41     */
42    private $token_verifier;
43
44    /**
45     * Google client instance.
46     *
47     * @var GoogleClient
48     */
49    private $google_client;
50
51    /**
52     * Authenticator service.
53     *
54     * @var Authenticator
55     */
56    private $authenticator;
57
58    /**
59     * OneTapLogin constructor.
60     *
61     * @param Settings      $settings Settings object.
62     * @param TokenVerifier $verifier Token verifier object.
63     * @param GoogleClient  $client   Google client instance.
64     * @param Authenticator $authenticator Authenticator service instance.
65     */
66    public function __construct( Settings $settings, TokenVerifier $verifier, GoogleClient $client, Authenticator $authenticator ) {
67        $this->settings       = $settings;
68        $this->token_verifier = $verifier;
69        $this->google_client  = $client;
70        $this->authenticator  = $authenticator;
71    }
72
73    /**
74     * Module name.
75     *
76     * @return string
77     */
78    public function name(): string {
79        return 'one_tap_login';
80    }
81
82    /**
83     * Module Initialization activity.
84     *
85     * Everything will happen if and only if one tap is active in settings.
86     */
87    public function init(): void {
88        if ( $this->settings->one_tap_login ) {
89            add_action( 'login_enqueue_scripts', [ $this, 'one_tap_scripts' ] );
90            add_action( 'login_footer', [ $this, 'one_tap_prompt' ] );
91            add_action( 'wp_ajax_nopriv_validate_id_token', [ $this, 'validate_token' ] );
92            add_action( 'rtcamp.id_token_verified', [ $this, 'authenticate' ] );
93            add_action(
94                'init',
95                function () {
96                    if ( ! is_user_logged_in() ) {
97                        $hook_prefix = ( 'sitewide' === $this->settings->one_tap_login_screen ) ? 'wp' : 'login';
98                        add_action( $hook_prefix . '_enqueue_scripts', [ $this, 'one_tap_scripts' ] );
99                        add_action( $hook_prefix . '_footer', [ $this, 'one_tap_prompt' ], 10000 );
100                    }
101                }
102            );
103        }
104    }
105
106    /**
107     * Show one tap prompt markup.
108     *
109     * @return void
110     */
111    public function one_tap_prompt(): void {
112        ?>
113        <div id="g_id_onload" data-use_fedcm_for_prompt="true" data-client_id="<?php echo esc_attr( $this->settings->client_id ); ?>" data-login_uri="<?php echo esc_attr( wp_login_url() ); ?>" data-callback="LoginWithGoogleDataCallBack"></div>
114        <?php
115    }
116
117    /**
118     * Enqueue one-tap related scripts.
119     *
120     * @return void
121     */
122    public function one_tap_scripts(): void {
123        $filename = ( defined( 'WP_SCRIPT_DEBUG' ) && true === WP_SCRIPT_DEBUG ) ? 'onetap.min.js' : 'onetap.js';
124
125        wp_enqueue_script(
126            'login-with-google-one-tap',
127            'https://accounts.google.com/gsi/client',
128            [],
129            filemtime( trailingslashit( plugin()->path ) . 'assets/build/js/onetap.js' ),
130            true
131        );
132
133        $data = [
134            'ajaxurl' => admin_url( 'admin-ajax.php' ),
135            'state'   => $this->google_client->state(),
136            'homeurl' => get_option( 'home', '' ),
137        ];
138
139        wp_register_script(
140            'login-with-google-one-tap-js',
141            trailingslashit( plugin()->url ) . 'assets/build/js/' . $filename,
142            [
143                'wp-i18n',
144            ],
145            filemtime( trailingslashit( plugin()->path ) . 'assets/build/js/onetap.js' ),
146            true
147        );
148
149        wp_add_inline_script(
150            'login-with-google-one-tap-js',
151            'var TempAccessOneTap=' . json_encode( $data ), //phpcs:disable WordPress.WP.AlternativeFunctions.json_encode_json_encode
152            'before'
153        );
154
155        wp_enqueue_script( 'login-with-google-one-tap-js' );
156    }
157
158    /**
159     * Validate the ID token.
160     *
161     * @return void
162     * @throws Exception Credential verification failure exception.
163     */
164    public function validate_token(): void {
165        try {
166            $token    = Helper::filter_input( INPUT_POST, 'token', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
167            $verified = $this->token_verifier->verify_token( $token );
168
169            if ( ! $verified ) {
170                throw new Exception( __( 'Cannot verify the credentials', 'login-with-google' ) );
171            }
172
173            /**
174             * Do something when token has been verified successfully.
175             *
176             * If we are here that means ID token has been verified.
177             *
178             * @since 1.0.16
179             */
180            do_action( 'rtcamp.id_token_verified' );
181
182            $redirect_to   = apply_filters( 'rtcamp.google_default_redirect', admin_url() );
183            $state         = Helper::filter_input( INPUT_POST, 'state', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
184            $decoded_state = $state ? (array) ( json_decode( base64_decode( $state ) ) ) : null;    // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
185
186            if ( is_array( $decoded_state ) && ! empty( $decoded_state['provider'] ) && 'google' === $decoded_state['provider'] ) {
187                $redirect_to = $decoded_state['redirect_to'] ?? $redirect_to;
188            }
189
190            wp_send_json_success(
191                [
192                    'redirect' => $redirect_to,
193                ]
194            );
195            die;
196
197        } catch ( Exception $e ) {
198            wp_send_json_error( $e->getMessage() );
199        }
200    }
201
202    /**
203     * Authenticate the user in WordPress.
204     *
205     * @return void
206     * @throws Exception Authentication exception.
207     */
208    public function authenticate(): void {
209        $user = $this->token_verifier->current_user();
210
211        if ( is_null( $user ) ) {
212            throw new Exception( __( 'User not found to authenticate', 'login-with-google' ) );
213        }
214
215        $wp_user = $this->authenticator->authenticate( $user );
216        $this->authenticator->set_auth_cookies( $wp_user );
217    }
218}