33namespace OCA \Solid \Controller \ServerController ;
44
55use OCA \Solid \AppInfo \Application ;
6- use OCA \Solid \BaseServerConfig ;
76use OCA \Solid \Controller \ServerController ;
87use OCA \Solid \Service \UserService ;
8+ use OCP \AppFramework \Http \JSONResponse ;
99use OCP \IConfig ;
1010use OCP \IDBConnection ;
1111use OCP \IRequest ;
1212use OCP \ISession ;
1313use OCP \IURLGenerator ;
1414use OCP \IUserManager ;
15+ use PHPUnit \Framework \MockObject \MockObject ;
1516use PHPUnit \Framework \TestCase ;
1617
1718/**
1819 * @coversDefaultClass \OCA\Solid\Controller\ServerController
1920 * @covers ::__construct
2021 *
2122 * @uses \OCA\Solid\Controller\ServerController
23+ * @uses \OCA\Solid\BaseServerConfig
24+ * @uses \OCA\Solid\JtiReplayDetector
25+ * @uses \OCA\Solid\ServerConfig
2226 */
2327class ServerControllerTest extends TestCase
2428{
2529 ////////////////////////////////// FIXTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
2630
2731 private const MOCK_CLIENT_ID = 'mock-client-id ' ;
28- private static string $ encryptionKey ;
32+ private const MOCK_USER_ID = 'mock user id ' ;
33+
2934 private static string $ privateKey ;
30- private static string $ publicKey ;
35+
36+ private IConfig |MockObject $ mockConfig ;
37+ private IUserManager |MockObject $ mockUserManager ;
3138
3239
3340 public static function setUpBeforeClass (): void
3441 {
3542 $ keyPath = __DIR__ . '/../../fixtures/keys ' ;
3643 self ::$ privateKey = file_get_contents ($ keyPath . '/private.key ' );
3744 }
45+
3846 /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
3947
4048 /**
@@ -44,19 +52,9 @@ public static function setUpBeforeClass(): void
4452 *
4553 * @dataProvider provideConstructorParameterIndex
4654 */
47- public function testInstatiationWithoutRequiredParameter ($ index )
55+ public function testInstantiationWithoutRequiredParameter ($ index )
4856 {
49- $ parameters = [
50- 'mock appname ' ,
51- $ this ->createMock (IRequest::class),
52- $ this ->createMock (ISession::class),
53- $ this ->createMock (IUserManager::class),
54- $ this ->createMock (IURLGenerator::class),
55- 'mock user id ' ,
56- $ this ->createMock (IConfig::class),
57- $ this ->createMock (UserService::class),
58- $ this ->createMock (IDBConnection::class),
59- ];
57+ $ parameters = $ this ->createMockConstructorParameters ();
6058
6159 $ parameters = array_slice ($ parameters , 0 , $ index );
6260
@@ -83,44 +81,195 @@ public function testInstatiationWithoutRequiredParameter($index)
8381 * @testdox ServerController should be instantiable with all required parameters
8482 *
8583 * @covers ::__construct
84+ */
85+ public function testInstantiation ()
86+ {
87+ $ parameters = $ this ->createMockConstructorParameters ();
88+
89+ $ controller = new ServerController (...$ parameters );
90+
91+ $ this ->assertInstanceOf (ServerController::class, $ controller );
92+ }
93+
94+ /**
95+ * @testdox ServerController should return a 401 when asked to authorize without signed-in user
96+ *
97+ * @covers ::authorize
98+ */
99+ public function testAuthorizeWithoutUser ()
100+ {
101+ $ parameters = $ this ->createMockConstructorParameters ();
102+
103+ $ controller = new ServerController (...$ parameters );
104+
105+ $ expected = new JSONResponse ('Authorization required ' , 401 );
106+ $ actual = $ controller ->authorize ();
107+
108+ $ this ->assertEquals ($ expected , $ actual );
109+ }
110+
111+ /**
112+ * @testdox ServerController should return a 400 when asked to authorize with a user but without valid token
113+ *
114+ * @covers ::authorize
115+ */
116+ public function testAuthorizeWithoutValidToken ()
117+ {
118+ $ _GET ['response_type ' ] = 'mock-response-type ' ;
119+
120+ $ parameters = $ this ->createMockConstructorParameters ();
121+
122+ $ this ->mockUserManager ->method ('userExists ' )->willReturn (true );
123+
124+ $ controller = new ServerController (...$ parameters );
125+
126+ $ actual = $ controller ->authorize ();
127+ $ expected = new JSONResponse ('Bad request, does not contain valid token ' , 400 );
128+
129+ $ this ->assertEquals ($ expected , $ actual );
130+ }
131+
132+ /**
133+ * @testdox ServerController should return a 302 redirect when asked to authorize client that has not been approved
134+ *
135+ * @covers ::authorize
136+ */
137+ public function testAuthorizeWithoutApprovedClient ()
138+ {
139+ $ _GET ['client_id ' ] = self ::MOCK_CLIENT_ID ;
140+ $ _GET ['nonce ' ] = 'mock-nonce ' ;
141+ // JWT with empty payload, HS256 encoded, created with `private.key` from fixtures
142+ $ _GET ['request ' ] = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.8VKCTiBegJPuPIZlp0wbV0Sbdn5BS6TE5DCx6oYNc5o ' ;
143+ $ _GET ['response_type ' ] = 'mock-response-type ' ;
144+
145+ $ _SERVER ['REQUEST_URI ' ] = 'mock uri ' ;
146+
147+ $ parameters = $ this ->createMockConstructorParameters ();
148+
149+ $ this ->mockConfig ->method ('getUserValue ' )->willReturnArgument (3 );
150+
151+ $ this ->mockUserManager ->method ('userExists ' )->willReturn (true );
152+
153+ $ controller = new ServerController (...$ parameters );
154+
155+ $ actual = $ controller ->authorize ();
156+ $ expected = new JSONResponse ('Approval required ' , 302 , ['Location ' => '' ]);
157+
158+ $ this ->assertEquals ($ expected , $ actual );
159+ }
160+
161+ /**
162+ * @testdox ServerController should return a 302 redirect when asked to authorize client that has been approved
86163 *
87- * @uses \League\OAuth2\Server\AuthorizationServer
88- * @uses \OCA\Solid\BaseServerConfig
89- * @uses \OCA\Solid\JtiReplayDetector
90- * @uses \OCA\Solid\ServerConfig
91- * @uses \Pdsinterop\Solid\Auth\Factory\AuthorizationServerFactory
92- * @uses \Pdsinterop\Solid\Auth\Factory\GrantTypeFactory
93- * @uses \Pdsinterop\Solid\Auth\Factory\RepositoryFactory
94- * @uses \Pdsinterop\Solid\Auth\TokenGenerator
164+ * @covers ::authorize
95165 */
96- public function testInstatiation ()
166+ public function testAuthorizeWithApprovedClient ()
97167 {
98- $ configMock = $ this ->createMock (IConfig::class);
168+ $ _GET ['client_id ' ] = self ::MOCK_CLIENT_ID ;
169+ $ _GET ['nonce ' ] = 'mock-nonce ' ;
170+ // JWT with empty payload, HS256 encoded, created with `private.key` from fixtures
171+ $ _GET ['request ' ] = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.8VKCTiBegJPuPIZlp0wbV0Sbdn5BS6TE5DCx6oYNc5o ' ;
172+ $ _GET ['response_type ' ] = 'mock-response-type ' ;
173+
174+ $ _SERVER ['REQUEST_URI ' ] = 'https://mock.server ' ;
175+
176+ $ clientData = json_encode (['client_name ' => 'Mock Client ' , 'redirect_uris ' => ['https://mock.client/redirect ' ]]);
177+
178+ $ parameters = $ this ->createMockConstructorParameters ($ clientData );
179+
180+ $ this ->mockConfig ->method ('getUserValue ' )
181+ ->with (self ::MOCK_USER_ID , Application::APP_ID , 'allowedClients ' , '[] ' )
182+ ->willReturn (json_encode ([self ::MOCK_CLIENT_ID ]));
183+
184+ $ this ->mockUserManager ->method ('userExists ' )->willReturn (true );
185+
186+ $ controller = new ServerController (...$ parameters );
187+
188+ $ response = $ controller ->authorize ();
189+
190+ $ expected = [
191+ 'data ' => 'ok ' ,
192+ 'headers ' => [
193+ 'Cache-Control ' => 'no-cache, no-store, must-revalidate ' ,
194+ 'Content-Security-Policy ' => "default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none' " ,
195+ 'Feature-Policy ' => "autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none' " ,
196+ 'X-Robots-Tag ' => 'noindex, nofollow ' ,
197+ 'Content-Type ' => 'application/json; charset=utf-8 ' ,
99198
100- $ configMock ->method ('getAppValue ' )->willReturnMap ([
101- [Application::APP_ID , 'client- ' . self ::MOCK_CLIENT_ID , '{} ' , 'return ' => '{} ' ],
102- [Application::APP_ID , 'client-d6d7896757f61ac4c397d914053180ff ' , '{} ' , 'return ' => '{} ' ],
103- [Application::APP_ID , 'client- ' , '{} ' , 'return ' => '{} ' ],
199+ ],
200+ 'status ' => 302 ,
201+ ];
202+
203+ $ headers = $ response ->getHeaders ();
204+ $ location = $ headers ['Location ' ];
205+ // Not comparing time-sensitive data
206+ unset($ headers ['X-Request-Id ' ], $ headers ['Location ' ]);
207+
208+ $ actual = [
209+ 'data ' => $ response ->getData (),
210+ 'headers ' => $ headers ,
211+ 'status ' => $ response ->getStatus (),
212+ ];
213+
214+ $ this ->assertEquals ($ expected , $ actual );
215+
216+ // @TODO: Move $location assert to a separate test
217+ $ url = parse_url ($ location );
218+
219+ parse_str ($ url ['fragment ' ], $ url ['fragment ' ]);
220+
221+ unset($ url ['fragment ' ]['access_token ' ], $ url ['fragment ' ]['id_token ' ]);
222+
223+ $ this ->assertEquals ([
224+ 'scheme ' => 'https ' ,
225+ 'host ' => 'mock.client ' ,
226+ 'path ' => '/redirect ' ,
227+ 'fragment ' => [
228+ 'token_type ' =>'Bearer ' ,
229+ 'expires_in ' =>'3600 ' ,
230+ ],
231+ ], $ url );
232+ }
233+
234+ ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
235+
236+ public function createMockConfig ($ clientData ): IConfig |MockObject
237+ {
238+ $ this ->mockConfig = $ this ->createMock (IConfig::class);
239+
240+ $ this ->mockConfig ->method ('getAppValue ' )->willReturnMap ([
241+ [Application::APP_ID , 'client- ' . self ::MOCK_CLIENT_ID , '{} ' , 'return ' => $ clientData ],
242+ [Application::APP_ID , 'client-d6d7896757f61ac4c397d914053180ff ' , '{} ' , 'return ' => $ clientData ],
243+ [Application::APP_ID , 'client- ' , '{} ' , 'return ' => $ clientData ],
104244 [Application::APP_ID , 'profileData ' , '' , 'return ' => '' ],
105245 [Application::APP_ID , 'encryptionKey ' , '' , 'return ' => 'mock encryption key ' ],
106246 [Application::APP_ID , 'privateKey ' , '' , 'return ' => self ::$ privateKey ],
107247 ]);
108248
249+ return $ this ->mockConfig ;
250+ }
251+ public function createMockConstructorParameters ($ clientData = '{} ' ): array
252+ {
109253 $ parameters = [
110254 'mock appname ' ,
111255 $ this ->createMock (IRequest::class),
112256 $ this ->createMock (ISession::class),
113- $ this ->createMock (IUserManager::class ),
257+ $ this ->createMockUserManager ( ),
114258 $ this ->createMock (IURLGenerator::class),
115- ' mock user id ' ,
116- $ configMock ,
259+ self :: MOCK_USER_ID ,
260+ $ this -> createMockConfig ( $ clientData ) ,
117261 $ this ->createMock (UserService::class),
118262 $ this ->createMock (IDBConnection::class),
119263 ];
120264
121- $ controller = new ServerController (...$ parameters );
265+ return $ parameters ;
266+ }
122267
123- $ this ->assertInstanceOf (ServerController::class, $ controller );
268+ public function createMockUserManager (): IUserManager |MockObject
269+ {
270+ $ this ->mockUserManager = $ this ->createMock (IUserManager::class);
271+
272+ return $ this ->mockUserManager ;
124273 }
125274
126275 /////////////////////////////// DATAPROVIDERS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
0 commit comments