@@ -15,6 +15,7 @@ import java.nio.file.spi.FileSystemProvider
1515import java .time .temporal .ChronoUnit
1616import java .time .{Duration , OffsetDateTime }
1717import java .util .UUID
18+ import scala .collection .mutable
1819import scala .jdk .CollectionConverters ._
1920import scala .util .{Failure , Success , Try }
2021
@@ -160,12 +161,14 @@ object BlobSasTokenGenerator {
160161 */
161162 def createBlobTokenGenerator (workspaceManagerClient : WorkspaceManagerApiClientProvider ,
162163 overrideWsmAuthToken : Option [String ]): BlobSasTokenGenerator = {
163- WSMBlobSasTokenGenerator (workspaceManagerClient, overrideWsmAuthToken)
164+ new WSMBlobSasTokenGenerator (workspaceManagerClient, overrideWsmAuthToken)
164165 }
165166
166167}
167168
168- case class WSMBlobSasTokenGenerator (wsmClientProvider : WorkspaceManagerApiClientProvider ,
169+ case class WSMTerraCoordinates (wsmEndpoint : String , workspaceId : UUID , containerResourceId : UUID )
170+
171+ class WSMBlobSasTokenGenerator (wsmClientProvider : WorkspaceManagerApiClientProvider ,
169172 overrideWsmAuthToken : Option [String ]) extends BlobSasTokenGenerator {
170173
171174 /**
@@ -178,17 +181,14 @@ case class WSMBlobSasTokenGenerator(wsmClientProvider: WorkspaceManagerApiClient
178181 * @return an AzureSasCredential for accessing a blob container
179182 */
180183 def generateBlobSasToken (endpoint : EndpointURL , container : BlobContainerName ): Try [AzureSasCredential ] = {
181- val wsmAuthToken : Try [String ] = overrideWsmAuthToken match {
182- case Some (t) => Success (t)
183- case None => AzureCredentials .getAccessToken(None ).toTry
184- }
184+ val wsmAuthToken : Try [String ] = getWsmAuth
185185 container.workspaceId match {
186186 // If this is a Terra workspace, request a token from WSM
187187 case Success (workspaceId) => {
188188 (for {
189189 wsmAuth <- wsmAuthToken
190190 wsmAzureResourceClient = wsmClientProvider.getControlledAzureResourceApi(wsmAuth)
191- resourceId <- getContainerResourceId(workspaceId, container, wsmAuth)
191+ resourceId <- getContainerResourceId(workspaceId, container, Option ( wsmAuth) )
192192 sasToken <- wsmAzureResourceClient.createAzureStorageContainerSasToken(workspaceId, resourceId)
193193 } yield sasToken).recoverWith {
194194 // If the storage account was still not found in WSM, this may be a public filesystem
@@ -201,9 +201,59 @@ case class WSMBlobSasTokenGenerator(wsmClientProvider: WorkspaceManagerApiClient
201201 }
202202 }
203203
204- def getContainerResourceId (workspaceId : UUID , container : BlobContainerName , wsmAuth : String ): Try [UUID ] = {
205- val wsmResourceClient = wsmClientProvider.getResourceApi(wsmAuth)
206- wsmResourceClient.findContainerResourceId(workspaceId, container)
204+ private val cachedContainerResourceIds = new mutable.HashMap [BlobContainerName , UUID ]()
205+
206+ // Optionally provide wsmAuth to avoid acquiring it twice in generateBlobSasToken.
207+ // In the case that the resourceId is not cached and no auth is provided, this function will acquire a new auth as necessary.
208+ private def getContainerResourceId (workspaceId : UUID , container : BlobContainerName , precomputedWsmAuth : Option [String ]): Try [UUID ] = {
209+ cachedContainerResourceIds.get(container) match {
210+ case Some (id) => Try (id) // cache hit
211+ case _ => { // cache miss
212+ val auth : Try [String ] = precomputedWsmAuth.map(auth => Try (auth)).getOrElse(getWsmAuth)
213+ val resourceId = for {
214+ wsmAuth <- auth
215+ wsmResourceApi = wsmClientProvider.getResourceApi(wsmAuth)
216+ resourceId <- wsmResourceApi.findContainerResourceId(workspaceId, container)
217+ } yield resourceId
218+ resourceId.map(id => cachedContainerResourceIds.put(container, id)) // NB: Modifying cache state here.
219+ cachedContainerResourceIds.get(container) match {
220+ case Some (uuid) => Try (uuid)
221+ case _ => Failure (new NoSuchElementException (" Could not retrieve container resource ID from WSM" ))
222+ }
223+ }
224+ }
225+ }
226+
227+ private def getWsmAuth : Try [String ] = {
228+ overrideWsmAuthToken match {
229+ case Some (t) => Success (t)
230+ case None => AzureCredentials .getAccessToken(None ).toTry
231+ }
232+ }
233+
234+ private def parseTerraWorkspaceIdFromPath (blobPath : BlobPath ): Try [UUID ] = {
235+ if (blobPath.container.value.startsWith(" sc-" )) Try (UUID .fromString(blobPath.container.value.substring(3 )))
236+ else Failure (new Exception (" Could not parse workspace ID from storage container. Are you sure this is a file in a Terra Workspace?" ))
237+ }
238+
239+ /**
240+ * Return a REST endpoint that will reply with a sas token for the blob storage container associated with the provided blob path.
241+ * @param blobPath A blob path of a file living in a blob container that WSM knows about (likely a workspace container).
242+ * @param tokenDuration How long will the token last after being generated. Default is 8 hours. Sas tokens won't last longer than 24h.
243+ * NOTE: If a blobPath is provided for a file in a container other than what this token generator was constructed for,
244+ * this function will make two REST requests. Otherwise, the relevant data is already cached locally.
245+ */
246+ def getWSMSasFetchEndpoint (blobPath : BlobPath , tokenDuration : Option [Duration ] = None ): Try [String ] = {
247+ val wsmEndpoint = wsmClientProvider.getBaseWorkspaceManagerUrl
248+ val lifetimeQueryParameters : String = tokenDuration.map(d => s " ?sasExpirationDuration= ${d.toSeconds.intValue}" ).getOrElse(" " )
249+ val terraInfo : Try [WSMTerraCoordinates ] = for {
250+ workspaceId <- parseTerraWorkspaceIdFromPath(blobPath)
251+ containerResourceId <- getContainerResourceId(workspaceId, blobPath.container, None )
252+ coordinates = WSMTerraCoordinates (wsmEndpoint, workspaceId, containerResourceId)
253+ } yield coordinates
254+ terraInfo.map{terraCoordinates =>
255+ s " ${terraCoordinates.wsmEndpoint}/api/workspaces/v1/ ${terraCoordinates.workspaceId.toString}/resources/controlled/azure/storageContainer/ ${terraCoordinates.containerResourceId.toString}/getSasToken ${lifetimeQueryParameters}"
256+ }
207257 }
208258}
209259
0 commit comments