1010use Exception ;
1111use Icewind \Streams \CallbackWrapper ;
1212use Icewind \Streams \IteratorDirectory ;
13+ use NCU \Security \Signature \ISignatureManager ;
1314use OC \Files \Filesystem ;
1415use OC \MemCache \ArrayCache ;
16+ use OC \OCM \OCMSignatoryManager ;
1517use OCP \AppFramework \Http ;
1618use OCP \Constants ;
1719use OCP \Diagnostics \IEventLogger ;
2224use OCP \Files \StorageNotAvailableException ;
2325use OCP \Http \Client \IClient ;
2426use OCP \Http \Client \IClientService ;
27+ use OCP \IAppConfig ;
2528use OCP \ICertificateManager ;
2629use OCP \IConfig ;
30+ use OCP \OCM \Exceptions \OCMArgumentException ;
31+ use OCP \OCM \Exceptions \OCMProviderException ;
32+ use OCP \OCM \IOCMDiscoveryService ;
2733use OCP \Server ;
2834use OCP \Util ;
2935use Psr \Http \Message \ResponseInterface ;
3440use Sabre \HTTP \ClientHttpException ;
3541use Sabre \HTTP \RequestInterface ;
3642
37- /*
43+ /**
3844 * Class BearerAuthAwareSabreClient
3945 *
4046 * This is an extension of the Sabre HTTP Client
@@ -104,6 +110,10 @@ class DAV extends Common {
104110 protected LoggerInterface $ logger ;
105111 protected IEventLogger $ eventLogger ;
106112 protected IMimeTypeDetector $ mimeTypeDetector ;
113+ protected IOCMDiscoveryService $ discoveryService ;
114+ protected ISignatureManager $ signatureManager ;
115+ protected OCMSignatoryManager $ signatoryManager ;
116+ protected IAppConfig $ appConfig ;
107117
108118 /** @var int */
109119 private $ timeout ;
@@ -126,6 +136,11 @@ class DAV extends Common {
126136 public function __construct (array $ parameters ) {
127137 $ this ->statCache = new ArrayCache ();
128138 $ this ->httpClientService = Server::get (IClientService::class);
139+ if (isset ($ parameters ['discoveryService ' ])) {
140+ $ this ->discoveryService = $ parameters ['discoveryService ' ];
141+ } else {
142+ $ this ->discoveryService = Server::get (IOCMDiscoveryService::class);
143+ }
129144 if (isset ($ parameters ['host ' ]) && isset ($ parameters ['user ' ]) && isset ($ parameters ['password ' ])) {
130145 $ host = $ parameters ['host ' ];
131146 //remove leading http[s], will be generated in createBaseUri()
@@ -164,6 +179,9 @@ public function __construct(array $parameters) {
164179 // This timeout value will be used for the download and upload of files
165180 $ this ->timeout = Server::get (IConfig::class)->getSystemValueInt ('davstorage.request_timeout ' , IClient::DEFAULT_REQUEST_TIMEOUT );
166181 $ this ->mimeTypeDetector = \OC ::$ server ->getMimeTypeDetector ();
182+ $ this ->signatureManager = Server::get (ISignatureManager::class);
183+ $ this ->signatoryManager = Server::get (OCMSignatoryManager::class);
184+ $ this ->appConfig = Server::get (IAppConfig::class);
167185 }
168186
169187 protected function init (): void {
@@ -172,9 +190,82 @@ protected function init(): void {
172190 }
173191 $ this ->ready = true ;
174192
193+ // If using Bearer auth, exchange refresh token for access token
194+ $ userName = $ this ->user ;
195+ if ($ this ->authType !== null && ($ this ->authType & BearerAuthAwareSabreClient::AUTH_BEARER )) {
196+ try {
197+ $ host = 'https:// ' . $ this ->host ;
198+ $ ocmProvider = $ this ->discoveryService ->discover ($ host );
199+ $ tokenEndpoint = $ ocmProvider ->getTokenEndPoint ();
200+
201+ if ($ tokenEndPoint === '' ) {
202+ $ this ->logger ->error ('OCM provider response missing tokenEndPoint ' , ['app ' => 'dav ' ]);
203+ throw new StorageNotAvailableException ('Could not discover token endpoint ' );
204+ }
205+
206+ $ client = $ this ->httpClientService ->newClient ();
207+ $ payload = [
208+ 'grant_type ' => 'authorization_code ' ,
209+ 'client_id ' => 'receiver.example.org ' ,
210+ 'code ' => $ this ->user ,
211+ ];
212+
213+ $ options = [
214+ 'body ' => json_encode ($ payload ),
215+ 'headers ' => [
216+ 'Content-Type ' => 'application/json ' ,
217+ ],
218+ 'timeout ' => 10 ,
219+ 'connect_timeout ' => 10 ,
220+ ];
221+
222+ // Try signing the request
223+ if (!$ this ->appConfig ->getValueBool ('core ' , OCMSignatoryManager::APPCONFIG_SIGN_DISABLED , lazy: true )) {
224+ try {
225+ $ options = $ this ->signatureManager ->signOutgoingRequestIClientPayload (
226+ $ this ->signatoryManager ,
227+ $ options ,
228+ 'post ' ,
229+ $ tokenEndpoint
230+ );
231+ $ this ->logger ->debug ('Token request signed successfully ' , ['app ' => 'dav ' ]);
232+ } catch (\Exception $ e ) {
233+ $ this ->logger ->warning ('Failed to sign token request, continuing without signature ' , [
234+ 'app ' => 'dav ' ,
235+ 'exception ' => $ e ,
236+ 'endpoint ' => $ tokenEndpoint ,
237+ ]);
238+ }
239+ }
240+
241+ $ response = $ client ->post ($ tokenEndpoint , $ options );
242+
243+ $ body = $ response ->getBody ();
244+ $ data = json_decode ($ body , true );
245+
246+ if (isset ($ data ['access_token ' ])) {
247+ $ userName = $ data ['access_token ' ];
248+ $ this ->user = $ userName ;
249+ $ this ->logger ->debug ('Successfully exchanged refresh token for access token ' , ['app ' => 'dav ' ]);
250+ } else {
251+ $ this ->logger ->error ('Failed to get access token from response ' , ['app ' => 'dav ' ]);
252+ throw new StorageNotAvailableException ('Could not obtain access token ' );
253+ }
254+ } catch (OCMProviderException |OCMArgumentException $ e ) {
255+ $ this ->logger ->error ('OCM provider response missing tokenEndPoint ' , ['app ' => 'dav ' ]);
256+ throw new StorageNotAvailableException ('Could not discover token endpoint ' );
257+ } catch (\Exception $ e ) {
258+ $ this ->logger ->error ('Error exchanging refresh token for access token: ' . $ e ->getMessage (), [
259+ 'app ' => 'dav ' ,
260+ 'exception ' => $ e ,
261+ ]);
262+ throw new StorageNotAvailableException ('Could not obtain access token: ' . $ e ->getMessage ());
263+ }
264+ }
265+
175266 $ settings = [
176267 'baseUri ' => $ this ->createBaseUri (),
177- 'userName ' => $ this -> user ,
268+ 'userName ' => $ userName ,
178269 'password ' => $ this ->password ,
179270 ];
180271 if ($ this ->authType !== null ) {
@@ -186,7 +277,7 @@ protected function init(): void {
186277 $ settings ['proxy ' ] = $ proxy ;
187278 }
188279
189- $ this ->client = new Client ($ settings );
280+ $ this ->client = new BearerAuthAwareSabreClient ($ settings );
190281 $ this ->client ->setThrowExceptions (true );
191282
192283 if ($ this ->secure === true ) {
@@ -362,10 +453,14 @@ public function fopen(string $path, string $mode) {
362453 case 'r ' :
363454 case 'rb ' :
364455 try {
456+ $ auth = [$ this ->user , $ this ->password ];
457+ if ($ this ->authType === BearerAuthAwareSabreClient::AUTH_BEARER ) {
458+ $ auth = [$ this ->user , '' , 'bearer ' ];
459+ }
365460 $ response = $ this ->httpClientService
366461 ->newClient ()
367462 ->get ($ this ->createBaseUri () . $ this ->encodePath ($ path ), [
368- 'auth ' => [ $ this -> user , $ this -> password ] ,
463+ 'auth ' => $ auth ,
369464 'stream ' => true ,
370465 // set download timeout for users with slow connections or large files
371466 'timeout ' => $ this ->timeout
@@ -512,11 +607,15 @@ protected function uploadFile(string $path, string $target): void {
512607 $ this ->statCache ->remove ($ target );
513608 $ source = fopen ($ path , 'r ' );
514609
610+ $ auth = [$ this ->user , $ this ->password ];
611+ if ($ this ->authType === BearerAuthAwareSabreClient::AUTH_BEARER ) {
612+ $ auth = [$ this ->user , '' , 'bearer ' ];
613+ }
515614 $ this ->httpClientService
516615 ->newClient ()
517616 ->put ($ this ->createBaseUri () . $ this ->encodePath ($ target ), [
518617 'body ' => $ source ,
519- 'auth ' => [ $ this -> user , $ this -> password ] ,
618+ 'auth ' => $ auth ,
520619 // set upload timeout for users with slow connections or large files
521620 'timeout ' => $ this ->timeout
522621 ]);
0 commit comments