GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 37b02e...ebbbe1 )
by James
08:59
created

ProfileController::createOAuthKeys()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
nc 2
nop 0
1
<?php
2
/**
3
 * ProfileController.php
4
 * Copyright (c) 2017 [email protected]
5
 *
6
 * This file is part of Firefly III.
7
 *
8
 * Firefly III is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * Firefly III is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
20
 */
21
declare(strict_types=1);
22
23
namespace FireflyIII\Http\Controllers;
24
25
use Auth;
26
use DB;
27
use FireflyIII\Events\UserChangedEmail;
28
use FireflyIII\Exceptions\FireflyException;
29
use FireflyIII\Exceptions\ValidationException;
30
use FireflyIII\Http\Middleware\IsDemoUser;
31
use FireflyIII\Http\Middleware\IsSandStormUser;
32
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
33
use FireflyIII\Http\Requests\EmailFormRequest;
34
use FireflyIII\Http\Requests\ProfileFormRequest;
35
use FireflyIII\Http\Requests\TokenFormRequest;
36
use FireflyIII\Models\Preference;
37
use FireflyIII\Repositories\User\UserRepositoryInterface;
38
use FireflyIII\User;
39
use Google2FA;
40
use Hash;
41
use Illuminate\Contracts\Auth\Guard;
42
use Laravel\Passport\ClientRepository;
43
use Laravel\Passport\Passport;
44
use Log;
45
use phpseclib\Crypt\RSA;
46
use Preferences;
47
use Session;
48
use View;
49
50
/**
51
 * Class ProfileController.
52
 *
53
 * @method Guard guard()
54
 */
55
class ProfileController extends Controller
56
{
57
    /**
58
     * ProfileController constructor.
59
     */
60
    public function __construct()
61
    {
62
        parent::__construct();
63
64
        $this->middleware(
65
            function ($request, $next) {
66
                app('view')->share('title', trans('firefly.profile'));
67
                app('view')->share('mainTitleIcon', 'fa-user');
68
69
                return $next($request);
70
            }
71
        );
72
        $this->middleware(IsDemoUser::class)->except(['index']);
73
        $this->middleware(IsSandStormUser::class)->except('index');
74
    }
75
76
    /**
77
     * @return View
78
     */
79
    public function changeEmail()
80
    {
81
        $title        = auth()->user()->email;
82
        $email        = auth()->user()->email;
83
        $subTitle     = (string)trans('firefly.change_your_email');
84
        $subTitleIcon = 'fa-envelope';
85
86
        return view('profile.change-email', compact('title', 'subTitle', 'subTitleIcon', 'email'));
87
    }
88
89
    /**
90
     * @return View
91
     */
92
    public function changePassword()
93
    {
94
        $title        = auth()->user()->email;
95
        $subTitle     = (string)trans('firefly.change_your_password');
96
        $subTitleIcon = 'fa-key';
97
98
        return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon'));
99
    }
100
101
    /**
102
     * View that generates a 2FA code for the user.
103
     *
104
     * @return View
105
     */
106
    public function code()
107
    {
108
        $domain = $this->getDomain();
109
        $secret = Google2FA::generateSecretKey();
110
        Session::flash('two-factor-secret', $secret);
111
        $image = Google2FA::getQRCodeInline($domain, auth()->user()->email, $secret, 200);
112
113
        return view('profile.code', compact('image'));
114
    }
115
116
    /**
117
     * @param UserRepositoryInterface $repository
118
     * @param string                  $token
119
     *
120
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
121
     *
122
     * @throws FireflyException
123
     */
124
    public function confirmEmailChange(UserRepositoryInterface $repository, string $token)
125
    {
126
        // find preference with this token value.
127
        $set  = Preferences::findByName('email_change_confirm_token');
0 ignored issues
show
Bug introduced by
The method findByName() does not exist on FireflyIII\Support\Facades\Preferences. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

127
        /** @scrutinizer ignore-call */ 
128
        $set  = Preferences::findByName('email_change_confirm_token');
Loading history...
128
        $user = null;
129
        Log::debug(sprintf('Found %d preferences', $set->count()));
130
        /** @var Preference $preference */
131
        foreach ($set as $preference) {
132
            if ($preference->data === $token) {
133
                Log::debug('Found user');
134
                $user = $preference->user;
135
            }
136
        }
137
        // update user to clear blocked and blocked_code.
138
        if (null === $user) {
139
            Log::debug('Found no user');
140
            throw new FireflyException('Invalid token.');
141
        }
142
        Log::debug('Will unblock user.');
143
        $repository->unblockUser($user);
144
145
        // return to login.
146
        Session::flash('success', (string)trans('firefly.login_with_new_email'));
147
148
        return redirect(route('login'));
149
    }
150
151
    /**
152
     * @return View
153
     */
154
    public function deleteAccount()
155
    {
156
        $title        = auth()->user()->email;
157
        $subTitle     = (string)trans('firefly.delete_account');
158
        $subTitleIcon = 'fa-trash';
159
160
        return view('profile.delete-account', compact('title', 'subTitle', 'subTitleIcon'));
161
    }
162
163
    /**
164
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
165
     */
166
    public function deleteCode()
167
    {
168
        Preferences::delete('twoFactorAuthEnabled');
0 ignored issues
show
Bug introduced by
The method delete() does not exist on FireflyIII\Support\Facades\Preferences. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
        Preferences::/** @scrutinizer ignore-call */ 
169
                     delete('twoFactorAuthEnabled');
Loading history...
169
        Preferences::delete('twoFactorAuthSecret');
170
        Session::flash('success', (string)trans('firefly.pref_two_factor_auth_disabled'));
171
        Session::flash('info', (string)trans('firefly.pref_two_factor_auth_remove_it'));
172
173
        return redirect(route('profile.index'));
174
    }
175
176
    /**
177
     * @param UserRepositoryInterface $repository
178
     *
179
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
180
     */
181
    public function enable2FA(UserRepositoryInterface $repository)
182
    {
183
        if ($repository->hasRole(auth()->user(), 'demo')) {
184
            return redirect(route('profile.index'));
185
        }
186
        $hasTwoFactorAuthSecret = (null !== Preferences::get('twoFactorAuthSecret'));
0 ignored issues
show
Bug introduced by
The method get() does not exist on FireflyIII\Support\Facades\Preferences. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
        $hasTwoFactorAuthSecret = (null !== Preferences::/** @scrutinizer ignore-call */ get('twoFactorAuthSecret'));
Loading history...
187
188
        // if we don't have a valid secret yet, redirect to the code page to get one.
189
        if (!$hasTwoFactorAuthSecret) {
190
            return redirect(route('profile.code'));
191
        }
192
193
        // If FF3 already has a secret, just set the two factor auth enabled to 1,
194
        // and let the user continue with the existing secret.
195
196
        Preferences::set('twoFactorAuthEnabled', 1);
0 ignored issues
show
Bug introduced by
The method set() does not exist on FireflyIII\Support\Facades\Preferences. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

196
        Preferences::/** @scrutinizer ignore-call */ 
197
                     set('twoFactorAuthEnabled', 1);
Loading history...
197
198
        return redirect(route('profile.index'));
199
    }
200
201
    /**
202
     * @return View
203
     */
204
    public function index()
205
    {
206
        // check if client token thing exists (default one)
207
        $count = DB::table('oauth_clients')
208
                   ->where('personal_access_client', 1)
209
                   ->whereNull('user_id')->count();
210
211
        $this->createOAuthKeys();
212
213
        if ($count === 0) {
214
            /** @var ClientRepository $repository */
215
            $repository = app(ClientRepository::class);
216
            $repository->createPersonalAccessClient(null, config('app.name') . ' Personal Access Client', 'http://localhost');
217
        }
218
        $subTitle   = auth()->user()->email;
219
        $userId     = auth()->user()->id;
220
        $enabled2FA = (int)Preferences::get('twoFactorAuthEnabled', 0)->data === 1;
221
222
        // get access token or create one.
223
        $accessToken = Preferences::get('access_token', null);
224
        if (null === $accessToken) {
225
            $token       = auth()->user()->generateAccessToken();
226
            $accessToken = Preferences::set('access_token', $token);
227
        }
228
229
        return view('profile.index', compact('subTitle', 'userId', 'accessToken', 'enabled2FA'));
230
    }
231
232
    /**
233
     * @param EmailFormRequest        $request
234
     * @param UserRepositoryInterface $repository
235
     *
236
     * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
237
     */
238
    public function postChangeEmail(EmailFormRequest $request, UserRepositoryInterface $repository)
239
    {
240
        /** @var User $user */
241
        $user     = auth()->user();
242
        $newEmail = $request->string('email');
243
        $oldEmail = $user->email;
244
        if ($newEmail === $user->email) {
245
            Session::flash('error', (string)trans('firefly.email_not_changed'));
246
247
            return redirect(route('profile.change-email'))->withInput();
248
        }
249
        $existing = $repository->findByEmail($newEmail);
250
        if (null !== $existing) {
251
            // force user logout.
252
            Auth::guard()->logout();
253
            $request->session()->invalidate();
254
255
            Session::flash('success', (string)trans('firefly.email_changed'));
256
257
            return redirect(route('index'));
258
        }
259
260
        // now actually update user:
261
        $repository->changeEmail($user, $newEmail);
262
263
        // call event.
264
        $ipAddress = $request->ip();
265
        event(new UserChangedEmail($user, $newEmail, $oldEmail, $ipAddress));
266
267
        // force user logout.
268
        Auth::guard()->logout();
269
        $request->session()->invalidate();
270
        Session::flash('success', (string)trans('firefly.email_changed'));
271
272
        return redirect(route('index'));
273
    }
274
275
    /**
276
     * @param ProfileFormRequest      $request
277
     * @param UserRepositoryInterface $repository
278
     *
279
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
280
     */
281
    public function postChangePassword(ProfileFormRequest $request, UserRepositoryInterface $repository)
282
    {
283
        // the request has already validated both new passwords must be equal.
284
        $current = $request->get('current_password');
285
        $new     = $request->get('new_password');
286
287
        try {
288
            $this->validatePassword(auth()->user(), $current, $new);
289
        } catch (ValidationException $e) {
290
            Session::flash('error', $e->getMessage());
291
292
            return redirect(route('profile.change-password'));
293
        }
294
295
        $repository->changePassword(auth()->user(), $request->get('new_password'));
296
        Session::flash('success', (string)trans('firefly.password_changed'));
297
298
        return redirect(route('profile.index'));
299
    }
300
301
    /**
302
     * @param TokenFormRequest $request
303
     *
304
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
305
     * @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
306
     */
307
    public function postCode(TokenFormRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

307
    public function postCode(/** @scrutinizer ignore-unused */ TokenFormRequest $request)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
308
    {
309
        Preferences::set('twoFactorAuthEnabled', 1);
310
        Preferences::set('twoFactorAuthSecret', Session::get('two-factor-secret'));
311
312
        Session::flash('success', (string)trans('firefly.saved_preferences'));
313
        Preferences::mark();
0 ignored issues
show
Bug introduced by
The method mark() does not exist on FireflyIII\Support\Facades\Preferences. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

313
        Preferences::/** @scrutinizer ignore-call */ 
314
                     mark();
Loading history...
314
315
        return redirect(route('profile.index'));
316
    }
317
318
    /**
319
     * @param UserRepositoryInterface  $repository
320
     * @param DeleteAccountFormRequest $request
321
     *
322
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
323
     */
324
    public function postDeleteAccount(UserRepositoryInterface $repository, DeleteAccountFormRequest $request)
325
    {
326
        if (!Hash::check($request->get('password'), auth()->user()->password)) {
327
            Session::flash('error', (string)trans('firefly.invalid_password'));
328
329
            return redirect(route('profile.delete-account'));
330
        }
331
        $user = auth()->user();
332
        Log::info(sprintf('User #%d has opted to delete their account', auth()->user()->id));
333
        // make repository delete user:
334
        auth()->logout();
335
        Session::flush();
336
        $repository->destroy($user);
337
338
        return redirect(route('index'));
339
    }
340
341
    /**
342
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
343
     */
344
    public function regenerate()
345
    {
346
        $token = auth()->user()->generateAccessToken();
347
        Preferences::set('access_token', $token);
348
        Session::flash('success', (string)trans('firefly.token_regenerated'));
349
350
        return redirect(route('profile.index'));
351
    }
352
353
    /**
354
     * @param UserRepositoryInterface $repository
355
     * @param string                  $token
356
     * @param string                  $hash
357
     *
358
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
359
     *
360
     * @throws FireflyException
361
     */
362
    public function undoEmailChange(UserRepositoryInterface $repository, string $token, string $hash)
363
    {
364
        // find preference with this token value.
365
        $set  = Preferences::findByName('email_change_undo_token');
366
        $user = null;
367
        /** @var Preference $preference */
368
        foreach ($set as $preference) {
369
            if ($preference->data === $token) {
370
                $user = $preference->user;
371
            }
372
        }
373
        if (null === $user) {
374
            throw new FireflyException('Invalid token.');
375
        }
376
377
        // found user.
378
        // which email address to return to?
379
        $set = Preferences::beginsWith($user, 'previous_email_');
0 ignored issues
show
Bug introduced by
The method beginsWith() does not exist on FireflyIII\Support\Facades\Preferences. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

379
        /** @scrutinizer ignore-call */ 
380
        $set = Preferences::beginsWith($user, 'previous_email_');
Loading history...
380
        /** @var string $match */
381
        $match = null;
382
        foreach ($set as $entry) {
383
            $hashed = hash('sha256', $entry->data);
384
            if ($hashed === $hash) {
385
                $match = $entry->data;
386
                break;
387
            }
388
        }
389
        if (null === $match) {
390
            throw new FireflyException('Invalid token.');
391
        }
392
        // change user back
393
        // now actually update user:
394
        $repository->changeEmail($user, $match);
395
        $repository->unblockUser($user);
396
397
        // return to login.
398
        Session::flash('success', (string)trans('firefly.login_with_old_email'));
399
400
        return redirect(route('login'));
401
    }
402
403
    /**
404
     * @param User   $user
405
     * @param string $current
406
     * @param string $new
407
     *
408
     * @return bool
409
     *
410
     * @throws ValidationException
411
     */
412
    protected function validatePassword(User $user, string $current, string $new): bool
413
    {
414
        if (!Hash::check($current, $user->password)) {
415
            throw new ValidationException((string)trans('firefly.invalid_current_password'));
416
        }
417
418
        if ($current === $new) {
419
            throw new ValidationException((string)trans('firefly.should_change'));
420
        }
421
422
        return true;
423
    }
424
425
    /**
426
     *
427
     */
428
    private function createOAuthKeys()
429
    {
430
        $rsa  = new RSA();
431
        $keys = $rsa->createKey(4096);
432
433
        [$publicKey, $privateKey] = [
434
            Passport::keyPath('oauth-public.key'),
435
            Passport::keyPath('oauth-private.key'),
436
        ];
437
438
        if (file_exists($publicKey) || file_exists($privateKey)) {
439
            return;
440
        }
441
        Log::alert('NO OAuth keys were found. They have been created.');
442
443
        file_put_contents($publicKey, array_get($keys, 'publickey'));
444
        file_put_contents($privateKey, array_get($keys, 'privatekey'));
445
    }
446
447
    /**
448
     * @return string
449
     */
450
    private function getDomain(): string
451
    {
452
        $url   = url()->to('/');
453
        $parts = parse_url($url);
454
455
        return $parts['host'];
456
    }
457
}
458