@@ -100,6 +100,7 @@ pub struct BlobSharedAccessSignature {
100100 identifier : Option < String > ,
101101 ip : Option < String > ,
102102 protocol : Option < SasProtocol > ,
103+ signed_directory_depth : Option < usize > , // sdd
103104}
104105
105106impl BlobSharedAccessSignature {
@@ -120,6 +121,7 @@ impl BlobSharedAccessSignature {
120121 identifier : None ,
121122 ip : None ,
122123 protocol : None ,
124+ signed_directory_depth : None ,
123125 }
124126 }
125127
@@ -128,6 +130,7 @@ impl BlobSharedAccessSignature {
128130 identifier: String => Some ( identifier) ,
129131 ip: String => Some ( ip) ,
130132 protocol: SasProtocol => Some ( protocol) ,
133+ signed_directory_depth: usize => Some ( signed_directory_depth) ,
131134 }
132135
133136 fn sign ( & self ) -> String {
@@ -179,9 +182,78 @@ impl SasToken for BlobSharedAccessSignature {
179182 elements. push ( format ! ( "spr={protocol}" ) )
180183 }
181184
185+ if let Some ( signed_directory_depth) = & self . signed_directory_depth {
186+ elements. push ( format ! ( "sdd={signed_directory_depth}" ) )
187+ }
188+
182189 let sig = self . sign ( ) ;
183190 elements. push ( format ! ( "sig={}" , format_form( sig) ) ) ;
184191
185192 elements. join ( "&" )
186193 }
187194}
195+
196+ #[ cfg( test) ]
197+ mod test {
198+ use super :: * ;
199+ use std:: collections:: HashSet ;
200+ use time:: Duration ;
201+
202+ const MOCK_SECRET_KEY : & str = "RZfi3m1W7eyQ5zD4ymSmGANVdJ2SDQmg4sE89SW104s=" ;
203+ const MOCK_CANONICALIZED_RESOURCE : & str = "/blob/STORAGE_ACCOUNT_NAME/CONTAINER_NAME/" ;
204+
205+ #[ test]
206+ fn test_blob_scoped_sas_token ( ) {
207+ let permissions = BlobSasPermissions {
208+ read : true ,
209+ ..Default :: default ( )
210+ } ;
211+ let signed_token = BlobSharedAccessSignature :: new (
212+ String :: from ( MOCK_SECRET_KEY ) ,
213+ String :: from ( MOCK_CANONICALIZED_RESOURCE ) ,
214+ permissions,
215+ OffsetDateTime :: UNIX_EPOCH + Duration :: days ( 7 ) ,
216+ BlobSignedResource :: Blob ,
217+ )
218+ . token ( ) ;
219+
220+ // splitting by & is only safe if & is not part of any fields
221+ // if that changes in the future we might want to use a proper query string parser
222+ let elements = signed_token. split ( '&' ) . collect :: < HashSet < _ > > ( ) ;
223+
224+ // BlobSignedResource::Blob
225+ assert ! ( elements. contains( "sr=b" ) ) ;
226+ // signed_directory_depth NOT set
227+ assert ! ( !elements. iter( ) . any( |element| element. starts_with( "sdd=" ) ) ) ;
228+
229+ assert_eq ! ( signed_token, "sv=2020-06-12&sp=r&sr=b&se=1970-01-08T00%3A00%3A00Z&sig=alEGfKjtiLs5LO%2FyrfPkzjQBHbk4Uda9XOezbRyKwEM%3D" ) ;
230+ }
231+
232+ #[ test]
233+ fn test_directory_scoped_sas_token ( ) {
234+ let permissions = BlobSasPermissions {
235+ read : true ,
236+ ..Default :: default ( )
237+ } ;
238+ let signed_token = BlobSharedAccessSignature :: new (
239+ String :: from ( MOCK_SECRET_KEY ) ,
240+ String :: from ( MOCK_CANONICALIZED_RESOURCE ) ,
241+ permissions,
242+ OffsetDateTime :: UNIX_EPOCH + Duration :: days ( 7 ) ,
243+ BlobSignedResource :: Directory ,
244+ )
245+ . signed_directory_depth ( 2_usize )
246+ . token ( ) ;
247+
248+ // splitting by & is only safe if & is not part of any fields
249+ // if that changes in the future we might want to use a proper query string parser
250+ let elements = signed_token. split ( '&' ) . collect :: < HashSet < _ > > ( ) ;
251+
252+ // BlobSignedResource::Blob
253+ assert ! ( elements. contains( "sr=d" ) ) ;
254+ // signed_directory_depth = 2
255+ assert ! ( elements. contains( "sdd=2" ) ) ;
256+
257+ assert_eq ! ( signed_token, "sv=2020-06-12&sp=r&sr=d&se=1970-01-08T00%3A00%3A00Z&sdd=2&sig=e0eoY169%2Bex4AnI9ZAOiOaX49snoJiuvyJ22XV6qW2k%3D" ) ;
258+ }
259+ }
0 commit comments