Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
54 / 54
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ResetPasswordSecondaryAuthenticationProvider
100.00% covered (success)
100.00%
54 / 54
100.00% covered (success)
100.00%
6 / 6
25
100.00% covered (success)
100.00%
1 / 1
 getAuthenticationRequests
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beginSecondaryAuthentication
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 continueSecondaryAuthentication
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beginSecondaryAccountCreation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 continueSecondaryAccountCreation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 tryReset
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
1 / 1
20
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Auth;
8
9use MediaWiki\Logger\LoggerFactory;
10use MediaWiki\Message\Message;
11use MediaWiki\User\User;
12
13/**
14 * Reset the local password, if signalled via `$this->manager->setAuthenticationSessionData()`
15 *
16 * The authentication data key is 'reset-pass'; the data is an object with the
17 * following properties:
18 * - msg: Message object to display to the user
19 * - hard: Boolean, if true the reset cannot be skipped.
20 * - req: Optional PasswordAuthenticationRequest to use to actually reset the
21 *   password. Won't be displayed to the user.
22 *
23 * @ingroup Auth
24 * @since 1.27
25 */
26class ResetPasswordSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
27
28    /** @inheritDoc */
29    public function getAuthenticationRequests( $action, array $options ) {
30        return [];
31    }
32
33    /** @inheritDoc */
34    public function beginSecondaryAuthentication( $user, array $reqs ) {
35        return $this->tryReset( $user, $reqs );
36    }
37
38    /** @inheritDoc */
39    public function continueSecondaryAuthentication( $user, array $reqs ) {
40        return $this->tryReset( $user, $reqs );
41    }
42
43    /** @inheritDoc */
44    public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
45        return $this->tryReset( $user, $reqs );
46    }
47
48    /** @inheritDoc */
49    public function continueSecondaryAccountCreation( $user, $creator, array $reqs ) {
50        return $this->tryReset( $user, $reqs );
51    }
52
53    /**
54     * Try to reset the password
55     * @param User $user
56     * @param AuthenticationRequest[] $reqs
57     * @return AuthenticationResponse
58     */
59    protected function tryReset( User $user, array $reqs ) {
60        $data = $this->manager->getAuthenticationSessionData( 'reset-pass' );
61        if ( !$data ) {
62            return AuthenticationResponse::newAbstain();
63        }
64
65        if ( is_array( $data ) ) {
66            $data = (object)$data;
67        }
68        if ( !is_object( $data ) ) {
69            throw new \UnexpectedValueException( 'reset-pass is not valid' );
70        }
71
72        if ( !isset( $data->msg ) ) {
73            throw new \UnexpectedValueException( 'reset-pass msg is missing' );
74        } elseif ( !$data->msg instanceof Message ) {
75            throw new \UnexpectedValueException( 'reset-pass msg is not valid' );
76        } elseif ( !isset( $data->hard ) ) {
77            throw new \UnexpectedValueException( 'reset-pass hard is missing' );
78        } elseif ( isset( $data->req ) && (
79            !$data->req instanceof PasswordAuthenticationRequest ||
80            !array_key_exists( 'retype', $data->req->getFieldInfo() )
81        ) ) {
82            throw new \UnexpectedValueException( 'reset-pass req is not valid' );
83        }
84
85        if ( !$data->hard ) {
86            $req = ButtonAuthenticationRequest::getRequestByName( $reqs, 'skipReset' );
87            if ( $req ) {
88                $this->manager->removeAuthenticationSessionData( 'reset-pass' );
89                return AuthenticationResponse::newPass();
90            }
91        }
92
93        /** @var PasswordAuthenticationRequest $needReq */
94        $needReq = $data->req ?? new PasswordAuthenticationRequest();
95        '@phan-var PasswordAuthenticationRequest $needReq';
96        if ( !$needReq->action ) {
97            $needReq->action = AuthManager::ACTION_CHANGE;
98        }
99        $needReq->required = $data->hard ? AuthenticationRequest::REQUIRED
100            : AuthenticationRequest::OPTIONAL;
101        $needReqs = [ $needReq ];
102        if ( !$data->hard ) {
103            $needReqs[] = new ButtonAuthenticationRequest(
104                'skipReset',
105                wfMessage( 'authprovider-resetpass-skip-label' ),
106                wfMessage( 'authprovider-resetpass-skip-help' )
107            );
108        }
109
110        /** @var PasswordAuthenticationRequest $req */
111        $req = AuthenticationRequest::getRequestByClass( $reqs, get_class( $needReq ) );
112        '@phan-var PasswordAuthenticationRequest $req';
113        if ( !$req || !array_key_exists( 'retype', $req->getFieldInfo() ) ) {
114            return AuthenticationResponse::newUI( $needReqs, $data->msg, 'warning' );
115        }
116
117        if ( $req->password !== $req->retype ) {
118            return AuthenticationResponse::newUI( $needReqs, new Message( 'badretype' ), 'error' );
119        }
120
121        $req->username = $user->getName();
122        $status = $this->manager->allowsAuthenticationDataChange( $req );
123        if ( !$status->isGood() ) {
124            return AuthenticationResponse::newUI( $needReqs, $status->getMessage(), 'error' );
125        }
126        $scope = LoggerFactory::getContext()->addScoped( [
127            'context.passwordResetOnLogin' => $data->hard ? 'forced' : 'suggested',
128        ] );
129        $this->manager->changeAuthenticationData( $req );
130
131        $this->manager->removeAuthenticationSessionData( 'reset-pass' );
132        return AuthenticationResponse::newPass();
133    }
134}