github.com/jbking/gohan@v0.0.0-20151217002006-b41ccf1c2a96/server/resources/resource_management.go (about) 1 // Copyright (C) 2015 NTT Innovation Institute, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 // implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package resources 17 18 import ( 19 "fmt" 20 "strings" 21 22 "github.com/cloudwan/gohan/db" 23 "github.com/cloudwan/gohan/db/pagination" 24 "github.com/cloudwan/gohan/db/transaction" 25 "github.com/cloudwan/gohan/extension" 26 27 "github.com/cloudwan/gohan/schema" 28 "github.com/cloudwan/gohan/server/middleware" 29 "github.com/twinj/uuid" 30 ) 31 32 //ResourceProblem describes the kind of problem that occurred during resource manipulation. 33 type ResourceProblem int 34 35 //The possible resource problems 36 const ( 37 InternalServerError ResourceProblem = iota 38 WrongQuery 39 WrongData 40 NotFound 41 DeleteFailed 42 CreateFailed 43 UpdateFailed 44 hlsearch 45 46 Unauthorized 47 ) 48 49 // ResourceError is created when an anticipated problem has occured during resource manipulations. 50 // It contains the original error, a message to the user and an integer indicating the type of the problem. 51 type ResourceError struct { 52 error 53 Message string 54 Problem ResourceProblem 55 } 56 57 //NewResourceError returns a new resource error 58 func NewResourceError(err error, message string, problem ResourceProblem) ResourceError { 59 return ResourceError{err, message, problem} 60 } 61 62 // ExtensionError is created when a problem has occured during event handling. It contains the information 63 // required to reraise the javascript exception that caused this error. 64 type ExtensionError struct { 65 error 66 ExceptionInfo map[string]interface{} 67 } 68 69 //InTransaction executes function in the db transaction and set it to the context 70 func InTransaction(context middleware.Context, dataStore db.DB, f func() error) error { 71 if context["transaction"] != nil { 72 return fmt.Errorf("cannot create nested transaction") 73 } 74 aTransaction, err := dataStore.Begin() 75 if err != nil { 76 return fmt.Errorf("cannot create transaction: %v", err) 77 } 78 defer aTransaction.Close() 79 context["transaction"] = aTransaction 80 81 err = f() 82 if err != nil { 83 return err 84 } 85 86 err = aTransaction.Commit() 87 if err != nil { 88 return fmt.Errorf("commit error : %s", err) 89 } 90 delete(context, "transaction") 91 return nil 92 } 93 94 // ApplyPolicyForResources applies policy filtering for response 95 func ApplyPolicyForResources(context middleware.Context, resourceSchema *schema.Schema) error { 96 policy := context["policy"].(*schema.Policy) 97 rawResponse, ok := context["response"] 98 if !ok { 99 return fmt.Errorf("No response") 100 } 101 response, ok := rawResponse.(map[string]interface{}) 102 if !ok { 103 return fmt.Errorf("extension returned invalid JSON: %v", rawResponse) 104 } 105 resources, ok := response[resourceSchema.Plural].([]interface{}) 106 if !ok { 107 return nil 108 } 109 data := []interface{}{} 110 for _, resource := range resources { 111 data = append(data, policy.Filter(resource.(map[string]interface{}))) 112 } 113 response[resourceSchema.Plural] = data 114 return nil 115 } 116 117 // ApplyPolicyForResource applies policy filtering for response 118 func ApplyPolicyForResource(context middleware.Context, resourceSchema *schema.Schema) error { 119 policy := context["policy"].(*schema.Policy) 120 rawResponse, ok := context["response"] 121 if !ok { 122 return fmt.Errorf("No response") 123 } 124 response, ok := rawResponse.(map[string]interface{}) 125 if !ok { 126 return fmt.Errorf("extension returned invalid JSON: %v", rawResponse) 127 } 128 resource, ok := response[resourceSchema.Singular] 129 if !ok { 130 return nil 131 } 132 response[resourceSchema.Singular] = policy.Filter(resource.(map[string]interface{})) 133 return nil 134 } 135 136 //GetResources returns specified resources without calling non in_transaction events 137 func GetResources(context middleware.Context, dataStore db.DB, resourceSchema *schema.Schema, filter map[string]interface{}, paginator *pagination.Paginator) error { 138 return InTransaction( 139 context, dataStore, 140 func() error { 141 return GetResourcesInTransaction(context, resourceSchema, filter, paginator) 142 }, 143 ) 144 } 145 146 //GetResourcesInTransaction returns specified resources without calling non in_transaction events 147 func GetResourcesInTransaction(context middleware.Context, resourceSchema *schema.Schema, filter map[string]interface{}, paginator *pagination.Paginator) error { 148 mainTransaction := context["transaction"].(transaction.Transaction) 149 response := map[string]interface{}{} 150 151 environmentManager := extension.GetManager() 152 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 153 if !ok { 154 return fmt.Errorf("no environment for schema") 155 } 156 157 if err := extension.HandleEvent(context, environment, "pre_list_in_transaction"); err != nil { 158 return err 159 } 160 161 list, total, err := mainTransaction.List(resourceSchema, filter, paginator) 162 if err != nil { 163 response[resourceSchema.Plural] = []interface{}{} 164 context["response"] = response 165 return err 166 } 167 168 data := []interface{}{} 169 for _, resource := range list { 170 data = append(data, resource.Data()) 171 } 172 response[resourceSchema.Plural] = data 173 174 context["response"] = response 175 context["total"] = total 176 177 if err := extension.HandleEvent(context, environment, "post_list_in_transaction"); err != nil { 178 return err 179 } 180 return nil 181 } 182 183 //FilterFromQueryParameter makes list filter from query 184 func FilterFromQueryParameter(resourceSchema *schema.Schema, 185 queryParameters map[string][]string) map[string]interface{} { 186 filter := map[string]interface{}{} 187 for key, value := range queryParameters { 188 if _, err := resourceSchema.GetPropertyByID(key); err != nil { 189 log.Info("Resource %s does not have %s property, ignoring filter.") 190 continue 191 } 192 filter[key] = value 193 } 194 return filter 195 } 196 197 // GetMultipleResources returns all resources specified by the schema and query parameters 198 func GetMultipleResources(context middleware.Context, dataStore db.DB, resourceSchema *schema.Schema, queryParameters map[string][]string) error { 199 log.Debug("Start get multiple resources!!") 200 auth := context["auth"].(schema.Authorization) 201 policy, err := loadPolicy(context, "read", resourceSchema.GetPluralURL(), auth) 202 if err != nil { 203 return err 204 } 205 206 filter := FilterFromQueryParameter(resourceSchema, queryParameters) 207 208 if policy.RequireOwner() { 209 filter["tenant_id"] = policy.GetTenantIDFilter(schema.ActionRead, auth.TenantID()) 210 } 211 filter = policy.Filter(filter) 212 213 paginator, err := pagination.FromURLQuery(resourceSchema, queryParameters) 214 if err != nil { 215 return ResourceError{err, err.Error(), WrongQuery} 216 } 217 context["policy"] = policy 218 219 environmentManager := extension.GetManager() 220 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 221 if !ok { 222 return fmt.Errorf("No environment for schema") 223 } 224 if err := extension.HandleEvent(context, environment, "pre_list"); err != nil { 225 return err 226 } 227 if rawResponse, ok := context["response"]; ok { 228 if _, ok := rawResponse.(map[string]interface{}); ok { 229 return nil 230 } 231 return fmt.Errorf("extension returned invalid JSON: %v", rawResponse) 232 } 233 234 if err := GetResources(context, dataStore, resourceSchema, filter, paginator); err != nil { 235 return err 236 } 237 238 if err := extension.HandleEvent(context, environment, "post_list"); err != nil { 239 return err 240 } 241 242 if err := ApplyPolicyForResources(context, resourceSchema); err != nil { 243 return err 244 } 245 246 return nil 247 } 248 249 // GetSingleResource returns the resource specified by the schema and ID 250 func GetSingleResource(context middleware.Context, dataStore db.DB, resourceSchema *schema.Schema, resourceID string) error { 251 context["id"] = resourceID 252 auth := context["auth"].(schema.Authorization) 253 policy, err := loadPolicy(context, "read", strings.Replace(resourceSchema.GetSingleURL(), ":id", resourceID, 1), auth) 254 if err != nil { 255 return err 256 } 257 context["policy"] = policy 258 259 environmentManager := extension.GetManager() 260 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 261 if !ok { 262 return fmt.Errorf("No environment for schema") 263 } 264 if err := extension.HandleEvent(context, environment, "pre_show"); err != nil { 265 return err 266 } 267 if rawResponse, ok := context["response"]; ok { 268 if _, ok := rawResponse.(map[string]interface{}); ok { 269 return nil 270 } 271 return fmt.Errorf("extension returned invalid JSON: %v", rawResponse) 272 } 273 274 if err := InTransaction( 275 context, dataStore, 276 func() error { 277 return GetSingleResourceInTransaction(context, resourceSchema, resourceID, policy.GetTenantIDFilter(schema.ActionRead, auth.TenantID())) 278 }, 279 ); err != nil { 280 return err 281 } 282 283 if err := extension.HandleEvent(context, environment, "post_show"); err != nil { 284 return err 285 } 286 if err := ApplyPolicyForResource(context, resourceSchema); err != nil { 287 return err 288 } 289 return nil 290 } 291 292 //GetSingleResourceInTransaction get resource in single transaction 293 func GetSingleResourceInTransaction(context middleware.Context, resourceSchema *schema.Schema, resourceID string, tenantIDs []string) (err error) { 294 mainTransaction := context["transaction"].(transaction.Transaction) 295 environmentManager := extension.GetManager() 296 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 297 if !ok { 298 return fmt.Errorf("no environment for schema") 299 } 300 301 if err := extension.HandleEvent(context, environment, "pre_show_in_transaction"); err != nil { 302 return err 303 } 304 if rawResponse, ok := context["response"]; ok { 305 if _, ok := rawResponse.(map[string]interface{}); ok { 306 return nil 307 } 308 return fmt.Errorf("extension returned invalid JSON: %v", rawResponse) 309 } 310 object, err := mainTransaction.Fetch(resourceSchema, resourceID, tenantIDs) 311 312 if err != nil || object == nil { 313 return ResourceError{err, "", NotFound} 314 } 315 316 response := map[string]interface{}{} 317 response[resourceSchema.Singular] = object.Data() 318 context["response"] = response 319 320 if err := extension.HandleEvent(context, environment, "post_show_in_transaction"); err != nil { 321 return err 322 } 323 return 324 } 325 326 // CreateResource creates the resource specified by the schema and dataMap 327 func CreateResource( 328 context middleware.Context, 329 dataStore db.DB, 330 identityService middleware.IdentityService, 331 resourceSchema *schema.Schema, 332 dataMap map[string]interface{}, 333 ) error { 334 manager := schema.GetManager() 335 // Load environment 336 environmentManager := extension.GetManager() 337 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 338 if !ok { 339 return fmt.Errorf("No environment for schema") 340 } 341 auth := context["auth"].(schema.Authorization) 342 343 //LoadPolicy 344 policy, err := loadPolicy(context, "create", resourceSchema.GetPluralURL(), auth) 345 if err != nil { 346 return err 347 } 348 349 _, err = resourceSchema.GetPropertyByID("tenant_id") 350 if _, ok := dataMap["tenant_id"]; err == nil && !ok { 351 dataMap["tenant_id"] = context["tenant_id"] 352 } 353 354 if tenantID, ok := dataMap["tenant_id"]; ok { 355 dataMap["tenant_name"], err = identityService.GetTenantName(tenantID.(string)) 356 if err != nil { 357 return ResourceError{err, err.Error(), Unauthorized} 358 } 359 } 360 361 //Apply policy for api input 362 err = policy.Check(schema.ActionCreate, auth, dataMap) 363 if err != nil { 364 return ResourceError{err, err.Error(), Unauthorized} 365 } 366 delete(dataMap, "tenant_name") 367 368 context["resource"] = dataMap 369 if _, ok := dataMap["id"]; !ok { 370 dataMap["id"] = uuid.NewV4().String() 371 } 372 context["id"] = dataMap["id"] 373 374 if err := extension.HandleEvent(context, environment, "pre_create"); err != nil { 375 return err 376 } 377 378 if resourceData, ok := context["resource"].(map[string]interface{}); ok { 379 dataMap = resourceData 380 } 381 382 //Validation 383 err = resourceSchema.ValidateOnCreate(dataMap) 384 if err != nil { 385 return ResourceError{err, fmt.Sprintf("Validation error: %s", err), WrongData} 386 } 387 388 resource, err := manager.LoadResource(resourceSchema.ID, dataMap) 389 if err != nil { 390 return err 391 } 392 393 //Fillup default 394 err = resource.PopulateDefaults() 395 if err != nil { 396 return err 397 } 398 399 context["resource"] = resource.Data() 400 401 if err := InTransaction( 402 context, dataStore, 403 func() error { 404 return CreateResourceInTransaction(context, resource) 405 }, 406 ); err != nil { 407 return err 408 } 409 410 if err := extension.HandleEvent(context, environment, "post_create"); err != nil { 411 return err 412 } 413 414 if err := ApplyPolicyForResource(context, resourceSchema); err != nil { 415 return err 416 } 417 return nil 418 } 419 420 //CreateResourceInTransaction craete db resource model in transaction 421 func CreateResourceInTransaction(context middleware.Context, resource *schema.Resource) error { 422 resourceSchema := resource.Schema() 423 mainTransaction := context["transaction"].(transaction.Transaction) 424 environmentManager := extension.GetManager() 425 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 426 if !ok { 427 return fmt.Errorf("No environment for schema") 428 } 429 if err := extension.HandleEvent(context, environment, "pre_create_in_transaction"); err != nil { 430 return err 431 } 432 if err := mainTransaction.Create(resource); err != nil { 433 log.Debug("%s transaction error", err) 434 return ResourceError{ 435 err, 436 fmt.Sprintf("Failed to store data in database: %v", err), 437 CreateFailed} 438 } 439 440 response := map[string]interface{}{} 441 response[resourceSchema.Singular] = resource.Data() 442 context["response"] = response 443 444 if err := extension.HandleEvent(context, environment, "post_create_in_transaction"); err != nil { 445 return err 446 } 447 448 return nil 449 } 450 451 // UpdateResource updates the resource specified by the schema and ID using the dataMap 452 func UpdateResource( 453 context middleware.Context, 454 dataStore db.DB, identityService middleware.IdentityService, 455 resourceSchema *schema.Schema, 456 resourceID string, dataMap map[string]interface{}, 457 ) error { 458 459 context["id"] = resourceID 460 461 //load environment 462 environmentManager := extension.GetManager() 463 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 464 if !ok { 465 return fmt.Errorf("No environment for schema") 466 } 467 468 auth := context["auth"].(schema.Authorization) 469 470 //load policy 471 policy, err := loadPolicy(context, "update", strings.Replace(resourceSchema.GetSingleURL(), ":id", resourceID, 1), auth) 472 if err != nil { 473 return err 474 } 475 476 //fillup default values 477 if tenantID, ok := dataMap["tenant_id"]; ok { 478 dataMap["tenant_name"], err = identityService.GetTenantName(tenantID.(string)) 479 } 480 if err != nil { 481 return ResourceError{err, err.Error(), Unauthorized} 482 } 483 484 //check policy 485 err = policy.Check(schema.ActionUpdate, auth, dataMap) 486 delete(dataMap, "tenant_name") 487 if err != nil { 488 return ResourceError{err, err.Error(), Unauthorized} 489 } 490 context["resource"] = dataMap 491 492 if err := extension.HandleEvent(context, environment, "pre_update"); err != nil { 493 return err 494 } 495 496 if resourceData, ok := context["resource"].(map[string]interface{}); ok { 497 dataMap = resourceData 498 } 499 500 if err := InTransaction( 501 context, dataStore, 502 func() error { 503 return UpdateResourceInTransaction(context, resourceSchema, resourceID, dataMap, policy.GetTenantIDFilter(schema.ActionUpdate, auth.TenantID())) 504 }, 505 ); err != nil { 506 return err 507 } 508 509 if err := extension.HandleEvent(context, environment, "post_update"); err != nil { 510 return err 511 } 512 513 if err := ApplyPolicyForResource(context, resourceSchema); err != nil { 514 return err 515 } 516 return nil 517 } 518 519 // UpdateResourceInTransaction updates resource in db in transaction 520 func UpdateResourceInTransaction( 521 context middleware.Context, 522 resourceSchema *schema.Schema, resourceID string, 523 dataMap map[string]interface{}, tenantIDs []string) error { 524 525 manager := schema.GetManager() 526 mainTransaction := context["transaction"].(transaction.Transaction) 527 environmentManager := extension.GetManager() 528 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 529 if !ok { 530 return fmt.Errorf("No environment for schema") 531 } 532 resource, err := mainTransaction.Fetch( 533 resourceSchema, resourceID, tenantIDs) 534 if err != nil { 535 return ResourceError{err, err.Error(), WrongQuery} 536 } 537 err = resource.Update(dataMap) 538 if err != nil { 539 return ResourceError{err, err.Error(), WrongData} 540 } 541 context["resource"] = resource.Data() 542 543 if err := extension.HandleEvent(context, environment, "pre_update_in_transaction"); err != nil { 544 return err 545 } 546 547 dataMap, ok = context["resource"].(map[string]interface{}) 548 if !ok { 549 return fmt.Errorf("Resource not JSON: %s", err) 550 } 551 resource, err = manager.LoadResource(resourceSchema.ID, dataMap) 552 if err != nil { 553 return fmt.Errorf("Loading Resource failed: %s", err) 554 } 555 556 err = mainTransaction.Update(resource) 557 if err != nil { 558 return ResourceError{err, fmt.Sprintf("Failed to store data in database: %v", err), UpdateFailed} 559 } 560 561 response := map[string]interface{}{} 562 response[resourceSchema.Singular] = resource.Data() 563 context["response"] = response 564 565 if err := extension.HandleEvent(context, environment, "post_update_in_transaction"); err != nil { 566 return err 567 } 568 569 return nil 570 } 571 572 // DeleteResource deletes the resource specified by the schema and ID 573 func DeleteResource(context middleware.Context, 574 dataStore db.DB, 575 resourceSchema *schema.Schema, 576 resourceID string, 577 ) error { 578 context["id"] = resourceID 579 environmentManager := extension.GetManager() 580 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 581 if !ok { 582 return fmt.Errorf("No environment for schema") 583 } 584 auth := context["auth"].(schema.Authorization) 585 policy, err := loadPolicy(context, "delete", strings.Replace(resourceSchema.GetSingleURL(), ":id", resourceID, 1), auth) 586 if err != nil { 587 return err 588 } 589 590 preTransaction, err := dataStore.Begin() 591 if err != nil { 592 return fmt.Errorf("cannot create transaction: %v", err) 593 } 594 resource, fetchErr := preTransaction.Fetch(resourceSchema, resourceID, policy.GetTenantIDFilter(schema.ActionDelete, auth.TenantID())) 595 preTransaction.Close() 596 597 if resource != nil { 598 context["resource"] = resource.Data() 599 } 600 601 if err := extension.HandleEvent(context, environment, "pre_delete"); err != nil { 602 return err 603 } 604 if fetchErr != nil { 605 return ResourceError{err, "", NotFound} 606 } 607 if err := InTransaction( 608 context, dataStore, 609 func() error { 610 return DeleteResourceInTransaction(context, resourceSchema, resourceID) 611 }, 612 ); err != nil { 613 return err 614 } 615 if err := extension.HandleEvent(context, environment, "post_delete"); err != nil { 616 return err 617 } 618 return nil 619 } 620 621 //DeleteResourceInTransaction deletes resources in a transaction 622 func DeleteResourceInTransaction(context middleware.Context, resourceSchema *schema.Schema, resourceID string) error { 623 mainTransaction := context["transaction"].(transaction.Transaction) 624 environmentManager := extension.GetManager() 625 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 626 if !ok { 627 return fmt.Errorf("No environment for schema") 628 } 629 if err := extension.HandleEvent(context, environment, "pre_delete_in_transaction"); err != nil { 630 return err 631 } 632 633 err := mainTransaction.Delete(resourceSchema, resourceID) 634 if err != nil { 635 return ResourceError{err, "", DeleteFailed} 636 } 637 638 if err := extension.HandleEvent(context, environment, "post_delete_in_transaction"); err != nil { 639 return err 640 } 641 return nil 642 } 643 644 // ActionResource runs custom action on resource 645 func ActionResource(context middleware.Context, dataStore db.DB, identityService middleware.IdentityService, 646 resourceSchema *schema.Schema, action schema.Action, resourceID string, data interface{}, 647 ) error { 648 actionSchema := action.InputSchema 649 context["input"] = data 650 context["id"] = resourceID 651 652 environmentManager := extension.GetManager() 653 environment, ok := environmentManager.GetEnvironment(resourceSchema.ID) 654 if !ok { 655 return fmt.Errorf("No environment for schema") 656 } 657 658 err := resourceSchema.Validate(actionSchema, data) 659 if err != nil { 660 return ResourceError{err, fmt.Sprintf("Validation error: %s", err), WrongData} 661 } 662 663 if err := extension.HandleEvent(context, environment, action.ID); err != nil { 664 return err 665 } 666 667 if err := InTransaction(context, dataStore, func() error { 668 return extension.HandleEvent(context, environment, fmt.Sprintf("post_%s_in_transaction", action.ID)) 669 }); err != nil { 670 return err 671 } 672 673 if err := extension.HandleEvent(context, environment, fmt.Sprintf("post_%s", action.ID)); err != nil { 674 return err 675 } 676 677 if rawResponse, ok := context["response"]; ok { 678 if _, ok := rawResponse.(map[string]interface{}); ok { 679 return nil 680 } 681 return fmt.Errorf("extension returned invalid JSON: %v", rawResponse) 682 } 683 684 return fmt.Errorf("no response") 685 } 686 687 func loadPolicy(context middleware.Context, action, path string, auth schema.Authorization) (*schema.Policy, error) { 688 manager := schema.GetManager() 689 policy, role := manager.PolicyValidate(action, path, auth) 690 if policy == nil { 691 err := fmt.Errorf(fmt.Sprintf("No matching policy: %s %s", action, path)) 692 return nil, ResourceError{err, err.Error(), Unauthorized} 693 } 694 context["policy"] = policy 695 context["role"] = role 696 return policy, nil 697 }