Merge "AuthManager fixups around the login→RESTART→create flow"
[lhc/web/wiklou.git] / includes / auth / ConfirmLinkSecondaryAuthenticationProvider.php
1 <?php
2
3 namespace MediaWiki\Auth;
4
5 use StatusValue;
6 use User;
7
8 /**
9 * Links third-party authentication to the user's account
10 *
11 * If the user logged into linking provider accounts that aren't linked to a
12 * local user, this provider will prompt the user to link them after a
13 * successful login or account creation.
14 *
15 * To avoid confusing behavior, this provider should be later in the
16 * configuration list than any provider that can abort the authentication
17 * process, so that it is only invoked for successful authentication.
18 */
19 class ConfirmLinkSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
20
21 public function getAuthenticationRequests( $action, array $options ) {
22 return [];
23 }
24
25 public function beginSecondaryAuthentication( $user, array $reqs ) {
26 return $this->beginLinkAttempt( $user, 'AuthManager::authnState' );
27 }
28
29 public function continueSecondaryAuthentication( $user, array $reqs ) {
30 return $this->continueLinkAttempt( $user, 'AuthManager::authnState', $reqs );
31 }
32
33 public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
34 return $this->beginLinkAttempt( $user, 'AuthManager::accountCreationState' );
35 }
36
37 public function continueSecondaryAccountCreation( $user, $creator, array $reqs ) {
38 return $this->continueLinkAttempt( $user, 'AuthManager::accountCreationState', $reqs );
39 }
40
41 /**
42 * Begin the link attempt
43 * @param User $user
44 * @param string $key Session key to look in
45 * @return AuthenticationResponse
46 */
47 protected function beginLinkAttempt( $user, $key ) {
48 $session = $this->manager->getRequest()->getSession();
49 $state = $session->getSecret( $key );
50 if ( !is_array( $state ) ) {
51 return AuthenticationResponse::newAbstain();
52 }
53
54 $maybeLink = array_filter( $state['maybeLink'], function ( $req ) {
55 return $this->manager->allowsAuthenticationDataChange( $req )->isGood();
56 } );
57 if ( !$maybeLink ) {
58 return AuthenticationResponse::newAbstain();
59 }
60
61 $req = new ConfirmLinkAuthenticationRequest( $maybeLink );
62 return AuthenticationResponse::newUI(
63 [ $req ],
64 wfMessage( 'authprovider-confirmlink-message' )
65 );
66 }
67
68 /**
69 * Continue the link attempt
70 * @param User $user
71 * @param string $key Session key to look in
72 * @param AuthenticationRequest[] $reqs
73 * @return AuthenticationResponse
74 */
75 protected function continueLinkAttempt( $user, $key, array $reqs ) {
76 $req = ButtonAuthenticationRequest::getRequestByName( $reqs, 'linkOk' );
77 if ( $req ) {
78 return AuthenticationResponse::newPass();
79 }
80
81 $req = AuthenticationRequest::getRequestByClass( $reqs, ConfirmLinkAuthenticationRequest::class );
82 if ( !$req ) {
83 // WTF? Retry.
84 return $this->beginLinkAttempt( $user, $key );
85 }
86
87 $session = $this->manager->getRequest()->getSession();
88 $state = $session->getSecret( $key );
89 if ( !is_array( $state ) ) {
90 return AuthenticationResponse::newAbstain();
91 }
92
93 $maybeLink = [];
94 foreach ( $state['maybeLink'] as $linkReq ) {
95 $maybeLink[$linkReq->getUniqueId()] = $linkReq;
96 }
97 if ( !$maybeLink ) {
98 return AuthenticationResponse::newAbstain();
99 }
100
101 $state['maybeLink'] = [];
102 $session->setSecret( $key, $state );
103
104 $statuses = [];
105 $anyFailed = false;
106 foreach ( $req->confirmedLinkIDs as $id ) {
107 if ( isset( $maybeLink[$id] ) ) {
108 $req = $maybeLink[$id];
109 $req->username = $user->getName();
110 if ( !$req->action ) {
111 // Make sure the action is set, but don't override it if
112 // the provider filled it in.
113 $req->action = AuthManager::ACTION_CHANGE;
114 }
115 $status = $this->manager->allowsAuthenticationDataChange( $req );
116 $statuses[] = [ $req, $status ];
117 if ( $status->isGood() ) {
118 $this->manager->changeAuthenticationData( $req );
119 } else {
120 $anyFailed = true;
121 }
122 }
123 }
124 if ( !$anyFailed ) {
125 return AuthenticationResponse::newPass();
126 }
127
128 $combinedStatus = \Status::newGood();
129 foreach ( $statuses as $data ) {
130 list( $req, $status ) = $data;
131 $descriptionInfo = $req->describeCredentials();
132 $description = wfMessage(
133 'authprovider-confirmlink-option',
134 $descriptionInfo['provider']->text(), $descriptionInfo['account']->text()
135 )->text();
136 if ( $status->isGood() ) {
137 $combinedStatus->error( wfMessage( 'authprovider-confirmlink-success-line', $description ) );
138 } else {
139 $combinedStatus->error( wfMessage(
140 'authprovider-confirmlink-failed-line', $description, $status->getMessage()->text()
141 ) );
142 }
143 }
144 return AuthenticationResponse::newUI(
145 [
146 new ButtonAuthenticationRequest(
147 'linkOk', wfMessage( 'ok' ), wfMessage( 'authprovider-confirmlink-ok-help' )
148 )
149 ],
150 $combinedStatus->getMessage( 'authprovider-confirmlink-failed' )
151 );
152 }
153 }