table.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. package storage
  2. // Copyright 2017 Microsoft Corporation
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. import (
  16. "bytes"
  17. "encoding/json"
  18. "fmt"
  19. "io"
  20. "io/ioutil"
  21. "net/http"
  22. "net/url"
  23. "strconv"
  24. "strings"
  25. "time"
  26. )
  27. const (
  28. tablesURIPath = "/Tables"
  29. nextTableQueryParameter = "NextTableName"
  30. headerNextPartitionKey = "x-ms-continuation-NextPartitionKey"
  31. headerNextRowKey = "x-ms-continuation-NextRowKey"
  32. nextPartitionKeyQueryParameter = "NextPartitionKey"
  33. nextRowKeyQueryParameter = "NextRowKey"
  34. )
  35. // TableAccessPolicy are used for SETTING table policies
  36. type TableAccessPolicy struct {
  37. ID string
  38. StartTime time.Time
  39. ExpiryTime time.Time
  40. CanRead bool
  41. CanAppend bool
  42. CanUpdate bool
  43. CanDelete bool
  44. }
  45. // Table represents an Azure table.
  46. type Table struct {
  47. tsc *TableServiceClient
  48. Name string `json:"TableName"`
  49. OdataEditLink string `json:"odata.editLink"`
  50. OdataID string `json:"odata.id"`
  51. OdataMetadata string `json:"odata.metadata"`
  52. OdataType string `json:"odata.type"`
  53. }
  54. // EntityQueryResult contains the response from
  55. // ExecuteQuery and ExecuteQueryNextResults functions.
  56. type EntityQueryResult struct {
  57. OdataMetadata string `json:"odata.metadata"`
  58. Entities []*Entity `json:"value"`
  59. QueryNextLink
  60. table *Table
  61. }
  62. type continuationToken struct {
  63. NextPartitionKey string
  64. NextRowKey string
  65. }
  66. func (t *Table) buildPath() string {
  67. return fmt.Sprintf("/%s", t.Name)
  68. }
  69. func (t *Table) buildSpecificPath() string {
  70. return fmt.Sprintf("%s('%s')", tablesURIPath, t.Name)
  71. }
  72. // Get gets the referenced table.
  73. // See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/querying-tables-and-entities
  74. func (t *Table) Get(timeout uint, ml MetadataLevel) error {
  75. if ml == EmptyPayload {
  76. return errEmptyPayload
  77. }
  78. query := url.Values{
  79. "timeout": {strconv.FormatUint(uint64(timeout), 10)},
  80. }
  81. headers := t.tsc.client.getStandardHeaders()
  82. headers[headerAccept] = string(ml)
  83. uri := t.tsc.client.getEndpoint(tableServiceName, t.buildSpecificPath(), query)
  84. resp, err := t.tsc.client.exec(http.MethodGet, uri, headers, nil, t.tsc.auth)
  85. if err != nil {
  86. return err
  87. }
  88. defer resp.Body.Close()
  89. if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
  90. return err
  91. }
  92. respBody, err := ioutil.ReadAll(resp.Body)
  93. if err != nil {
  94. return err
  95. }
  96. err = json.Unmarshal(respBody, t)
  97. if err != nil {
  98. return err
  99. }
  100. return nil
  101. }
  102. // Create creates the referenced table.
  103. // This function fails if the name is not compliant
  104. // with the specification or the tables already exists.
  105. // ml determines the level of detail of metadata in the operation response,
  106. // or no data at all.
  107. // See https://docs.microsoft.com/rest/api/storageservices/fileservices/create-table
  108. func (t *Table) Create(timeout uint, ml MetadataLevel, options *TableOptions) error {
  109. uri := t.tsc.client.getEndpoint(tableServiceName, tablesURIPath, url.Values{
  110. "timeout": {strconv.FormatUint(uint64(timeout), 10)},
  111. })
  112. type createTableRequest struct {
  113. TableName string `json:"TableName"`
  114. }
  115. req := createTableRequest{TableName: t.Name}
  116. buf := new(bytes.Buffer)
  117. if err := json.NewEncoder(buf).Encode(req); err != nil {
  118. return err
  119. }
  120. headers := t.tsc.client.getStandardHeaders()
  121. headers = addReturnContentHeaders(headers, ml)
  122. headers = addBodyRelatedHeaders(headers, buf.Len())
  123. headers = options.addToHeaders(headers)
  124. resp, err := t.tsc.client.exec(http.MethodPost, uri, headers, buf, t.tsc.auth)
  125. if err != nil {
  126. return err
  127. }
  128. defer resp.Body.Close()
  129. if ml == EmptyPayload {
  130. if err := checkRespCode(resp, []int{http.StatusNoContent}); err != nil {
  131. return err
  132. }
  133. } else {
  134. if err := checkRespCode(resp, []int{http.StatusCreated}); err != nil {
  135. return err
  136. }
  137. }
  138. if ml != EmptyPayload {
  139. data, err := ioutil.ReadAll(resp.Body)
  140. if err != nil {
  141. return err
  142. }
  143. err = json.Unmarshal(data, t)
  144. if err != nil {
  145. return err
  146. }
  147. }
  148. return nil
  149. }
  150. // Delete deletes the referenced table.
  151. // This function fails if the table is not present.
  152. // Be advised: Delete deletes all the entries that may be present.
  153. // See https://docs.microsoft.com/rest/api/storageservices/fileservices/delete-table
  154. func (t *Table) Delete(timeout uint, options *TableOptions) error {
  155. uri := t.tsc.client.getEndpoint(tableServiceName, t.buildSpecificPath(), url.Values{
  156. "timeout": {strconv.Itoa(int(timeout))},
  157. })
  158. headers := t.tsc.client.getStandardHeaders()
  159. headers = addReturnContentHeaders(headers, EmptyPayload)
  160. headers = options.addToHeaders(headers)
  161. resp, err := t.tsc.client.exec(http.MethodDelete, uri, headers, nil, t.tsc.auth)
  162. if err != nil {
  163. return err
  164. }
  165. defer drainRespBody(resp)
  166. return checkRespCode(resp, []int{http.StatusNoContent})
  167. }
  168. // QueryOptions includes options for a query entities operation.
  169. // Top, filter and select are OData query options.
  170. type QueryOptions struct {
  171. Top uint
  172. Filter string
  173. Select []string
  174. RequestID string
  175. }
  176. func (options *QueryOptions) getParameters() (url.Values, map[string]string) {
  177. query := url.Values{}
  178. headers := map[string]string{}
  179. if options != nil {
  180. if options.Top > 0 {
  181. query.Add(OdataTop, strconv.FormatUint(uint64(options.Top), 10))
  182. }
  183. if options.Filter != "" {
  184. query.Add(OdataFilter, options.Filter)
  185. }
  186. if len(options.Select) > 0 {
  187. query.Add(OdataSelect, strings.Join(options.Select, ","))
  188. }
  189. headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
  190. }
  191. return query, headers
  192. }
  193. // QueryEntities returns the entities in the table.
  194. // You can use query options defined by the OData Protocol specification.
  195. //
  196. // See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/query-entities
  197. func (t *Table) QueryEntities(timeout uint, ml MetadataLevel, options *QueryOptions) (*EntityQueryResult, error) {
  198. if ml == EmptyPayload {
  199. return nil, errEmptyPayload
  200. }
  201. query, headers := options.getParameters()
  202. query = addTimeout(query, timeout)
  203. uri := t.tsc.client.getEndpoint(tableServiceName, t.buildPath(), query)
  204. return t.queryEntities(uri, headers, ml)
  205. }
  206. // NextResults returns the next page of results
  207. // from a QueryEntities or NextResults operation.
  208. //
  209. // See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/query-entities
  210. // See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-timeout-and-pagination
  211. func (eqr *EntityQueryResult) NextResults(options *TableOptions) (*EntityQueryResult, error) {
  212. if eqr == nil {
  213. return nil, errNilPreviousResult
  214. }
  215. if eqr.NextLink == nil {
  216. return nil, errNilNextLink
  217. }
  218. headers := options.addToHeaders(map[string]string{})
  219. return eqr.table.queryEntities(*eqr.NextLink, headers, eqr.ml)
  220. }
  221. // SetPermissions sets up table ACL permissions
  222. // See https://docs.microsoft.com/rest/api/storageservices/fileservices/Set-Table-ACL
  223. func (t *Table) SetPermissions(tap []TableAccessPolicy, timeout uint, options *TableOptions) error {
  224. params := url.Values{"comp": {"acl"},
  225. "timeout": {strconv.Itoa(int(timeout))},
  226. }
  227. uri := t.tsc.client.getEndpoint(tableServiceName, t.Name, params)
  228. headers := t.tsc.client.getStandardHeaders()
  229. headers = options.addToHeaders(headers)
  230. body, length, err := generateTableACLPayload(tap)
  231. if err != nil {
  232. return err
  233. }
  234. headers["Content-Length"] = strconv.Itoa(length)
  235. resp, err := t.tsc.client.exec(http.MethodPut, uri, headers, body, t.tsc.auth)
  236. if err != nil {
  237. return err
  238. }
  239. defer drainRespBody(resp)
  240. return checkRespCode(resp, []int{http.StatusNoContent})
  241. }
  242. func generateTableACLPayload(policies []TableAccessPolicy) (io.Reader, int, error) {
  243. sil := SignedIdentifiers{
  244. SignedIdentifiers: []SignedIdentifier{},
  245. }
  246. for _, tap := range policies {
  247. permission := generateTablePermissions(&tap)
  248. signedIdentifier := convertAccessPolicyToXMLStructs(tap.ID, tap.StartTime, tap.ExpiryTime, permission)
  249. sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier)
  250. }
  251. return xmlMarshal(sil)
  252. }
  253. // GetPermissions gets the table ACL permissions
  254. // See https://docs.microsoft.com/rest/api/storageservices/fileservices/get-table-acl
  255. func (t *Table) GetPermissions(timeout int, options *TableOptions) ([]TableAccessPolicy, error) {
  256. params := url.Values{"comp": {"acl"},
  257. "timeout": {strconv.Itoa(int(timeout))},
  258. }
  259. uri := t.tsc.client.getEndpoint(tableServiceName, t.Name, params)
  260. headers := t.tsc.client.getStandardHeaders()
  261. headers = options.addToHeaders(headers)
  262. resp, err := t.tsc.client.exec(http.MethodGet, uri, headers, nil, t.tsc.auth)
  263. if err != nil {
  264. return nil, err
  265. }
  266. defer resp.Body.Close()
  267. if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
  268. return nil, err
  269. }
  270. var ap AccessPolicy
  271. err = xmlUnmarshal(resp.Body, &ap.SignedIdentifiersList)
  272. if err != nil {
  273. return nil, err
  274. }
  275. return updateTableAccessPolicy(ap), nil
  276. }
  277. func (t *Table) queryEntities(uri string, headers map[string]string, ml MetadataLevel) (*EntityQueryResult, error) {
  278. headers = mergeHeaders(headers, t.tsc.client.getStandardHeaders())
  279. if ml != EmptyPayload {
  280. headers[headerAccept] = string(ml)
  281. }
  282. resp, err := t.tsc.client.exec(http.MethodGet, uri, headers, nil, t.tsc.auth)
  283. if err != nil {
  284. return nil, err
  285. }
  286. defer resp.Body.Close()
  287. if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
  288. return nil, err
  289. }
  290. data, err := ioutil.ReadAll(resp.Body)
  291. if err != nil {
  292. return nil, err
  293. }
  294. var entities EntityQueryResult
  295. err = json.Unmarshal(data, &entities)
  296. if err != nil {
  297. return nil, err
  298. }
  299. for i := range entities.Entities {
  300. entities.Entities[i].Table = t
  301. }
  302. entities.table = t
  303. contToken := extractContinuationTokenFromHeaders(resp.Header)
  304. if contToken == nil {
  305. entities.NextLink = nil
  306. } else {
  307. originalURI, err := url.Parse(uri)
  308. if err != nil {
  309. return nil, err
  310. }
  311. v := originalURI.Query()
  312. v.Set(nextPartitionKeyQueryParameter, contToken.NextPartitionKey)
  313. v.Set(nextRowKeyQueryParameter, contToken.NextRowKey)
  314. newURI := t.tsc.client.getEndpoint(tableServiceName, t.buildPath(), v)
  315. entities.NextLink = &newURI
  316. entities.ml = ml
  317. }
  318. return &entities, nil
  319. }
  320. func extractContinuationTokenFromHeaders(h http.Header) *continuationToken {
  321. ct := continuationToken{
  322. NextPartitionKey: h.Get(headerNextPartitionKey),
  323. NextRowKey: h.Get(headerNextRowKey),
  324. }
  325. if ct.NextPartitionKey != "" && ct.NextRowKey != "" {
  326. return &ct
  327. }
  328. return nil
  329. }
  330. func updateTableAccessPolicy(ap AccessPolicy) []TableAccessPolicy {
  331. taps := []TableAccessPolicy{}
  332. for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers {
  333. tap := TableAccessPolicy{
  334. ID: policy.ID,
  335. StartTime: policy.AccessPolicy.StartTime,
  336. ExpiryTime: policy.AccessPolicy.ExpiryTime,
  337. }
  338. tap.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r")
  339. tap.CanAppend = updatePermissions(policy.AccessPolicy.Permission, "a")
  340. tap.CanUpdate = updatePermissions(policy.AccessPolicy.Permission, "u")
  341. tap.CanDelete = updatePermissions(policy.AccessPolicy.Permission, "d")
  342. taps = append(taps, tap)
  343. }
  344. return taps
  345. }
  346. func generateTablePermissions(tap *TableAccessPolicy) (permissions string) {
  347. // generate the permissions string (raud).
  348. // still want the end user API to have bool flags.
  349. permissions = ""
  350. if tap.CanRead {
  351. permissions += "r"
  352. }
  353. if tap.CanAppend {
  354. permissions += "a"
  355. }
  356. if tap.CanUpdate {
  357. permissions += "u"
  358. }
  359. if tap.CanDelete {
  360. permissions += "d"
  361. }
  362. return permissions
  363. }