66 */
77namespace OCA \OAuth2 \Tests \Controller ;
88
9+ use OC \Authentication \Token \IProvider as IAuthTokenProvider ;
910use OCA \OAuth2 \Controller \SettingsController ;
1011use OCA \OAuth2 \Db \AccessTokenMapper ;
1112use OCA \OAuth2 \Db \Client ;
1213use OCA \OAuth2 \Db \ClientMapper ;
1314use OCP \AppFramework \Http ;
1415use OCP \AppFramework \Http \JSONResponse ;
15- use OCP \Authentication \Token \IProvider as IAuthTokenProvider ;
16+ use OCP \Authentication \Exceptions \WipeTokenException ;
17+ use OCP \Authentication \Token \IToken ;
1618use OCP \IL10N ;
1719use OCP \IRequest ;
1820use OCP \IUser ;
1921use OCP \IUserManager ;
2022use OCP \Security \ICrypto ;
2123use OCP \Security \ISecureRandom ;
2224use OCP \Server ;
25+ use PHPUnit \Framework \MockObject \MockObject ;
26+ use Psr \Log \LoggerInterface ;
2327use Test \TestCase ;
2428
2529#[\PHPUnit \Framework \Attributes \Group(name: 'DB ' )]
@@ -42,6 +46,7 @@ class SettingsControllerTest extends TestCase {
4246 private $ l ;
4347 /** @var ICrypto|\PHPUnit\Framework\MockObject\MockObject */
4448 private $ crypto ;
49+ private LoggerInterface &MockObject $ logger ;
4550
4651 protected function setUp (): void {
4752 parent ::setUp ();
@@ -53,6 +58,7 @@ protected function setUp(): void {
5358 $ this ->authTokenProvider = $ this ->createMock (IAuthTokenProvider::class);
5459 $ this ->userManager = $ this ->createMock (IUserManager::class);
5560 $ this ->crypto = $ this ->createMock (ICrypto::class);
61+ $ this ->logger = $ this ->createMock (LoggerInterface::class);
5662 $ this ->l = $ this ->createMock (IL10N ::class);
5763 $ this ->l ->method ('t ' )
5864 ->willReturnArgument (0 );
@@ -65,7 +71,8 @@ protected function setUp(): void {
6571 $ this ->l ,
6672 $ this ->authTokenProvider ,
6773 $ this ->userManager ,
68- $ this ->crypto
74+ $ this ->crypto ,
75+ $ this ->logger ,
6976 );
7077
7178 }
@@ -132,11 +139,15 @@ public function testDeleteClient(): void {
132139 $ user1 ->updateLastLoginTimestamp ();
133140 $ tokenProviderMock = $ this ->getMockBuilder (IAuthTokenProvider::class)->getMock ();
134141
135- // expect one call per user and ensure the correct client name
142+ // One getTokenByUser call per user; we return no matching tokens here
143+ // so invalidateTokenById is never invoked.
136144 $ tokenProviderMock
137145 ->expects ($ this ->exactly ($ count + 1 ))
138- ->method ('invalidateTokensOfUser ' )
139- ->with ($ this ->isType ('string ' ), 'My Client Name ' );
146+ ->method ('getTokenByUser ' )
147+ ->willReturn ([]);
148+ $ tokenProviderMock
149+ ->expects ($ this ->never ())
150+ ->method ('invalidateTokenById ' );
140151
141152 $ client = new Client ();
142153 $ client ->setId (123 );
@@ -167,7 +178,8 @@ public function testDeleteClient(): void {
167178 $ this ->l ,
168179 $ tokenProviderMock ,
169180 $ userManager ,
170- $ this ->crypto
181+ $ this ->crypto ,
182+ $ this ->logger ,
171183 );
172184
173185 $ result = $ settingsController ->deleteClient (123 );
@@ -177,6 +189,96 @@ public function testDeleteClient(): void {
177189 $ user1 ->delete ();
178190 }
179191
192+ public function testDeleteClientPreservesWipePendingToken (): void {
193+ $ userManager = Server::get (IUserManager::class);
194+ $ user = $ userManager ->createUser ('test_wipe_preserve ' , 'test_wipe_preserve ' );
195+ $ user ->updateLastLoginTimestamp ();
196+
197+ $ client = new Client ();
198+ $ client ->setId (456 );
199+ $ client ->setName ('My Client Name ' );
200+ $ client ->setRedirectUri ('https://example.com/ ' );
201+ $ client ->setSecret (bin2hex ('MyHashedSecret ' ));
202+ $ client ->setClientIdentifier ('MyClientIdentifier ' );
203+
204+ // Token marked for wipe with a matching client name: must NOT be invalidated.
205+ $ wipeToken = $ this ->createMock (IToken::class);
206+ $ wipeToken ->method ('getId ' )->willReturn (11 );
207+ $ wipeToken ->method ('getName ' )->willReturn ('My Client Name ' );
208+
209+ // Regular token with matching name: must be invalidated.
210+ $ regularToken = $ this ->createMock (IToken::class);
211+ $ regularToken ->method ('getId ' )->willReturn (12 );
212+ $ regularToken ->method ('getName ' )->willReturn ('My Client Name ' );
213+
214+ // Non-matching name: must be left alone.
215+ $ otherToken = $ this ->createMock (IToken::class);
216+ $ otherToken ->method ('getId ' )->willReturn (13 );
217+ $ otherToken ->method ('getName ' )->willReturn ('Some Other Client ' );
218+
219+ $ tokenProviderMock = $ this ->getMockBuilder (IAuthTokenProvider::class)->getMock ();
220+ $ tokenProviderMock
221+ ->method ('getTokenByUser ' )
222+ ->willReturnCallback (function (string $ uid ) use ($ wipeToken , $ regularToken , $ otherToken ) {
223+ return $ uid === 'test_wipe_preserve '
224+ ? [$ wipeToken , $ regularToken , $ otherToken ]
225+ : [];
226+ });
227+ // Wipe state is signalled via WipeTokenException from getTokenById.
228+ $ tokenProviderMock
229+ ->method ('getTokenById ' )
230+ ->willReturnCallback (function (int $ id ) use ($ wipeToken , $ regularToken ) {
231+ if ($ id === 11 ) {
232+ throw new WipeTokenException ($ wipeToken );
233+ }
234+ return $ regularToken ;
235+ });
236+ $ tokenProviderMock
237+ ->expects ($ this ->once ())
238+ ->method ('invalidateTokenById ' )
239+ ->with ('test_wipe_preserve ' , 12 );
240+
241+ $ this ->clientMapper
242+ ->method ('getByUid ' )
243+ ->with (456 )
244+ ->willReturn ($ client );
245+ $ this ->accessTokenMapper
246+ ->expects ($ this ->once ())
247+ ->method ('deleteByClientId ' )
248+ ->with (456 );
249+ $ this ->clientMapper
250+ ->expects ($ this ->once ())
251+ ->method ('delete ' )
252+ ->with ($ client );
253+
254+ $ logger = $ this ->createMock (LoggerInterface::class);
255+ $ logger ->expects ($ this ->atLeastOnce ())
256+ ->method ('info ' )
257+ ->with ($ this ->stringContains ('Preserving token ' ), $ this ->callback (function (array $ context ) {
258+ return ($ context ['tokenId ' ] ?? null ) === 11
259+ && ($ context ['uid ' ] ?? null ) === 'test_wipe_preserve ' ;
260+ }));
261+
262+ $ settingsController = new SettingsController (
263+ 'oauth2 ' ,
264+ $ this ->request ,
265+ $ this ->clientMapper ,
266+ $ this ->secureRandom ,
267+ $ this ->accessTokenMapper ,
268+ $ this ->l ,
269+ $ tokenProviderMock ,
270+ $ userManager ,
271+ $ this ->crypto ,
272+ $ logger ,
273+ );
274+
275+ $ result = $ settingsController ->deleteClient (456 );
276+ $ this ->assertInstanceOf (JSONResponse::class, $ result );
277+ $ this ->assertEquals ([], $ result ->getData ());
278+
279+ $ user ->delete ();
280+ }
281+
180282 public function testInvalidRedirectUri (): void {
181283 $ result = $ this ->settingsController ->addClient ('test ' , 'invalidurl ' );
182284
0 commit comments