github.com/readium/readium-lcp-server@v0.0.0-20240509124024-799e77a0bbd6/lsdserver/api/license_status.go (about) 1 // Copyright 2020 Readium Foundation. All rights reserved. 2 // Use of this source code is governed by a BSD-style license 3 // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 4 5 package apilsd 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "errors" 11 "io" 12 "log" 13 "net/http" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/gorilla/mux" 19 "github.com/jtacoma/uritemplates" 20 "github.com/readium/readium-lcp-server/api" 21 "github.com/readium/readium-lcp-server/config" 22 apilcp "github.com/readium/readium-lcp-server/lcpserver/api" 23 "github.com/readium/readium-lcp-server/license" 24 licensestatuses "github.com/readium/readium-lcp-server/license_statuses" 25 "github.com/readium/readium-lcp-server/logging" 26 "github.com/readium/readium-lcp-server/problem" 27 "github.com/readium/readium-lcp-server/status" 28 "github.com/readium/readium-lcp-server/transactions" 29 ) 30 31 // Server interface 32 type Server interface { 33 Transactions() transactions.Transactions 34 LicenseStatuses() licensestatuses.LicenseStatuses 35 GoofyMode() bool 36 } 37 38 // CreateLicenseStatusDocument creates a license status and adds it to database 39 // It is triggered by a notification from the license server 40 func CreateLicenseStatusDocument(w http.ResponseWriter, r *http.Request, s Server) { 41 var lic license.License 42 err := apilcp.DecodeJSONLicense(r, &lic) 43 if err != nil { 44 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 45 return 46 } 47 48 // add a log 49 logging.Print("Create a Status Doc for License " + lic.ID) 50 51 var ls licensestatuses.LicenseStatus 52 makeLicenseStatus(lic, &ls) 53 54 err = s.LicenseStatuses().Add(ls) 55 if err != nil { 56 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 57 return 58 } 59 60 // must come *after* w.Header().Add()/Set(), but before w.Write() 61 w.WriteHeader(http.StatusCreated) 62 } 63 64 // GetLicenseStatusDocument gets a license status from the db by license id 65 // checks potential_rights_end and fill it 66 func GetLicenseStatusDocument(w http.ResponseWriter, r *http.Request, s Server) { 67 vars := mux.Vars(r) 68 69 licenseID := vars["key"] 70 71 // add a log 72 logging.Print("Get a Status Doc for License " + licenseID) 73 74 licenseStatus, err := s.LicenseStatuses().GetByLicenseID(licenseID) 75 if err != nil { 76 if licenseStatus == nil { 77 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 78 return 79 } 80 81 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 82 return 83 } 84 85 currentDateTime := time.Now().UTC().Truncate(time.Second) 86 87 // if a rights end date is set, check if the license has expired 88 if licenseStatus.CurrentEndLicense != nil { 89 diff := currentDateTime.Sub(*(licenseStatus.CurrentEndLicense)) 90 91 // if the rights end date has passed for a ready or active license 92 if (diff > 0) && ((licenseStatus.Status == status.STATUS_ACTIVE) || (licenseStatus.Status == status.STATUS_READY)) { 93 // the license has expired 94 licenseStatus.Status = status.STATUS_EXPIRED 95 // set the updated status time 96 currentTime := time.Now().UTC().Truncate(time.Second) 97 licenseStatus.Updated.Status = ¤tTime 98 // update the db 99 err = s.LicenseStatuses().Update(*licenseStatus) 100 if err != nil { 101 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 102 return 103 } 104 } 105 } 106 107 err = fillLicenseStatus(licenseStatus, s) 108 if err != nil { 109 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 110 return 111 } 112 113 w.Header().Set("Content-Type", api.ContentType_LSD_JSON) 114 115 // the device count must not be sent in json to the caller 116 licenseStatus.DeviceCount = nil 117 enc := json.NewEncoder(w) 118 // write the JSON encoding of the license status to the stream, followed by a newline character 119 err = enc.Encode(licenseStatus) 120 if err != nil { 121 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 122 return 123 } 124 } 125 126 // RegisterDevice registers a device for a given license, 127 // using the device id & name as parameters; 128 // returns the updated license status 129 func RegisterDevice(w http.ResponseWriter, r *http.Request, s Server) { 130 131 w.Header().Set("Content-Type", api.ContentType_LSD_JSON) 132 vars := mux.Vars(r) 133 134 var msg string 135 136 // get the license id from the url 137 licenseID := vars["key"] 138 139 deviceID := r.FormValue("id") 140 deviceName := r.FormValue("name") 141 142 // add a log 143 logging.Print("Register the Device " + deviceName + " with id " + deviceID + " for License " + licenseID) 144 145 dILen := len(deviceID) 146 dNLen := len(deviceName) 147 148 // check the mandatory request parameters 149 if (dILen == 0) || (dILen > 255) || (dNLen == 0) || (dNLen > 255) { 150 msg = "device id and device name are mandatory and their maximum length is 255 bytes" 151 problem.Error(w, r, problem.Problem{Type: problem.REGISTRATION_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 152 return 153 } 154 155 // check the existence of the license in the lsd server 156 licenseStatus, err := s.LicenseStatuses().GetByLicenseID(licenseID) 157 if err != nil { 158 if licenseStatus == nil { 159 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 160 return 161 } 162 // unknown error 163 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 164 return 165 } 166 167 // in case we want to test the resilience of an app to registering failures 168 if s.GoofyMode() { 169 msg = "**goofy mode** registering error" 170 problem.Error(w, r, problem.Problem{Type: problem.REGISTRATION_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 171 return 172 } 173 174 // check the status of the license. 175 // the device cannot be registered if the license has been revoked, returned, cancelled or expired 176 if (licenseStatus.Status != status.STATUS_ACTIVE) && (licenseStatus.Status != status.STATUS_READY) { 177 msg = "License is neither ready or active" 178 problem.Error(w, r, problem.Problem{Type: problem.REGISTRATION_BAD_REQUEST, Detail: msg}, http.StatusForbidden) 179 return 180 } 181 182 // check if the device has already been registered for this license 183 deviceStatus, err := s.Transactions().CheckDeviceStatus(licenseStatus.ID, deviceID) 184 if err != nil { 185 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 186 return 187 } 188 if deviceStatus != "" { // this is not considered a server side error, even if the spec states that devices must not do it. 189 logging.Print("The Device has already been registered") 190 // a status document will be sent back to the caller 191 192 } else { 193 194 // create a registered event 195 event := makeEvent(status.STATUS_ACTIVE, deviceName, deviceID, licenseStatus.ID) 196 err = s.Transactions().Add(*event, status.STATUS_ACTIVE_INT) 197 if err != nil { 198 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 199 return 200 } 201 202 // the license has been updated, the corresponding field is set 203 licenseStatus.Updated.Status = &event.Timestamp 204 205 // license status set to active if it was ready 206 if licenseStatus.Status == status.STATUS_READY { 207 licenseStatus.Status = status.STATUS_ACTIVE 208 } 209 // one more device attached to this license 210 *licenseStatus.DeviceCount++ 211 212 // update the license status in db 213 err = s.LicenseStatuses().Update(*licenseStatus) 214 if err != nil { 215 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 216 return 217 } 218 // add a log 219 logging.Print("The new Device Count is " + strconv.Itoa(*licenseStatus.DeviceCount)) 220 221 } // the device has just been registered for this license 222 223 // the device has been registered for the license (now *or before*) 224 // fill the updated license status 225 err = fillLicenseStatus(licenseStatus, s) 226 if err != nil { 227 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 228 return 229 } 230 // the device count must not be sent back to the caller 231 licenseStatus.DeviceCount = nil 232 // send back the license status to the caller 233 enc := json.NewEncoder(w) 234 err = enc.Encode(licenseStatus) 235 if err != nil { 236 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 237 return 238 } 239 } 240 241 // LendingReturn checks that the calling device is activated, then modifies 242 // the end date associated with the given license & returns updated and filled license status 243 func LendingReturn(w http.ResponseWriter, r *http.Request, s Server) { 244 w.Header().Set("Content-Type", api.ContentType_LSD_JSON) 245 vars := mux.Vars(r) 246 licenseID := vars["key"] 247 248 var msg string 249 250 licenseStatus, err := s.LicenseStatuses().GetByLicenseID(licenseID) 251 if err != nil { 252 if licenseStatus == nil { 253 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 254 return 255 } 256 257 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 258 return 259 } 260 261 deviceID := r.FormValue("id") 262 deviceName := r.FormValue("name") 263 264 // check request parameters 265 if (len(deviceName) > 255) || (len(deviceID) > 255) { 266 msg = "device id and device name are mandatory and their maximum length is 255 bytes" 267 problem.Error(w, r, problem.Problem{Type: problem.RETURN_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 268 return 269 } 270 271 // add a log 272 logging.Print("Return the Publication from Device " + deviceName + " with id " + deviceID + " for License " + licenseID) 273 274 // check & set the status of the license status according to its current value 275 switch licenseStatus.Status { 276 case status.STATUS_READY: 277 licenseStatus.Status = status.STATUS_CANCELLED 278 case status.STATUS_ACTIVE: 279 licenseStatus.Status = status.STATUS_RETURNED 280 case status.STATUS_EXPIRED: 281 msg = "The license has already expired" 282 problem.Error(w, r, problem.Problem{Type: problem.RETURN_EXPIRED, Detail: msg}, http.StatusForbidden) 283 return 284 case status.STATUS_RETURNED: 285 msg = "The license has already been returned before" 286 problem.Error(w, r, problem.Problem{Type: problem.RETURN_ALREADY, Detail: msg}, http.StatusForbidden) 287 return 288 default: 289 msg = "The current license status is " + licenseStatus.Status + "; return forbidden" 290 problem.Error(w, r, problem.Problem{Type: problem.RETURN_BAD_REQUEST, Detail: msg}, http.StatusForbidden) 291 return 292 } 293 294 // create a return event 295 event := makeEvent(status.STATUS_RETURNED, deviceName, deviceID, licenseStatus.ID) 296 err = s.Transactions().Add(*event, status.STATUS_RETURNED_INT) 297 if err != nil { 298 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 299 return 300 } 301 302 // update a license via a call to the lcp Server 303 // the event date is sent to the lcp server, covers the case where the lsd server clock is badly sync'd with the lcp server clock 304 httpStatusCode, errorr := updateLicense(event.Timestamp, licenseID) 305 if errorr != nil { 306 problem.Error(w, r, problem.Problem{Detail: errorr.Error()}, http.StatusInternalServerError) 307 return 308 } 309 if httpStatusCode != http.StatusOK && httpStatusCode != http.StatusPartialContent { // 200, 206 310 errorr = errors.New("LCP license PATCH returned HTTP error code " + strconv.Itoa(httpStatusCode)) 311 312 problem.Error(w, r, problem.Problem{Type: problem.RETURN_BAD_REQUEST, Detail: errorr.Error()}, httpStatusCode) 313 return 314 } 315 licenseStatus.CurrentEndLicense = &event.Timestamp 316 317 // update the license status 318 licenseStatus.Updated.Status = &event.Timestamp 319 // update the license updated timestamp with the event date 320 licenseStatus.Updated.License = &event.Timestamp 321 // remove the potential end timestamp 322 licenseStatus.PotentialRights = nil 323 324 err = s.LicenseStatuses().Update(*licenseStatus) 325 if err != nil { 326 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 327 return 328 } 329 330 // fill the license status 331 err = fillLicenseStatus(licenseStatus, s) 332 if err != nil { 333 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 334 return 335 } 336 337 // the device count must not be sent in json to the caller 338 licenseStatus.DeviceCount = nil 339 enc := json.NewEncoder(w) 340 err = enc.Encode(licenseStatus) 341 342 if err != nil { 343 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 344 return 345 } 346 } 347 348 // LendingRenewal checks that the calling device is registered with the license, 349 // then modifies the end date associated with the license 350 // and returns an updated license status to the caller. 351 // the 'end' parameter is optional; if absent, the end date is computed from 352 // the current end date plus a configuration parameter. 353 // Note: as per the spec, a non-registered device can renew a loan. 354 func LendingRenewal(w http.ResponseWriter, r *http.Request, s Server) { 355 w.Header().Set("Content-Type", api.ContentType_LSD_JSON) 356 vars := mux.Vars(r) 357 358 var msg string 359 360 // get the license status by license id 361 licenseID := vars["key"] 362 363 deviceID := r.FormValue("id") 364 deviceName := r.FormValue("name") 365 366 // add a log 367 logging.Print("Renew the Loan from Device " + deviceName + " with id " + deviceID + " for License " + licenseID) 368 369 // check the request parameters 370 if (len(deviceName) > 255) || (len(deviceID) > 255) { 371 msg = "device id and device name are mandatory and their maximum length is 255 bytes" 372 problem.Error(w, r, problem.Problem{Type: problem.RENEW_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 373 return 374 } 375 376 // get the license status 377 licenseStatus, err := s.LicenseStatuses().GetByLicenseID(licenseID) 378 if err != nil { 379 if licenseStatus == nil { 380 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 381 return 382 } 383 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 384 return 385 } 386 387 // check that the license status is active. 388 // note: renewing an unactive (ready) license is forbidden 389 if licenseStatus.Status != status.STATUS_ACTIVE { 390 msg = "The current license status is " + licenseStatus.Status + "; renew forbidden" 391 problem.Error(w, r, problem.Problem{Type: problem.RENEW_BAD_REQUEST, Detail: msg}, http.StatusForbidden) 392 return 393 } 394 395 // check if the license contains a date end property 396 var currentEnd time.Time 397 if licenseStatus.CurrentEndLicense == nil || (*licenseStatus.CurrentEndLicense).IsZero() { 398 msg = "This license has no current end date; it cannot be renewed" 399 problem.Error(w, r, problem.Problem{Type: problem.RENEW_BAD_REQUEST, Detail: msg}, http.StatusForbidden) 400 return 401 } 402 currentEnd = *licenseStatus.CurrentEndLicense 403 log.Print("Current end date " + currentEnd.UTC().Format(time.RFC3339)) 404 405 var suggestedEnd time.Time 406 // check if the 'end' request parameter is empty 407 timeEndString := r.FormValue("end") 408 if timeEndString == "" { 409 // get the config parameter renew_days 410 renewDays := config.Config.LicenseStatus.RenewDays 411 if renewDays == 0 { 412 msg = "No explicit end value and no configured value" 413 problem.Error(w, r, problem.Problem{Detail: msg}, http.StatusInternalServerError) 414 return 415 } 416 // compute a suggested duration from the config value 417 suggestedDuration := 24 * time.Hour * time.Duration(renewDays) // nanoseconds 418 419 // compute the suggested end date from the current end date 420 suggestedEnd = currentEnd.Add(time.Duration(suggestedDuration)) 421 //log.Print("Default extension request until ", suggestedEnd.UTC().Format(time.RFC3339)) 422 423 // if the 'end' request parameter is set 424 } else { 425 var err error 426 suggestedEnd, err = time.Parse(time.RFC3339, timeEndString) 427 if err != nil { 428 problem.Error(w, r, problem.Problem{Type: problem.RENEW_BAD_REQUEST, Detail: err.Error()}, http.StatusBadRequest) 429 return 430 } 431 //log.Print("Explicit extension request until ", suggestedEnd.UTC().Format(time.RFC3339)) 432 } 433 434 // check the suggested end date vs the upper end date (which is already set in our implementation) 435 //log.Print("Potential rights end = ", licenseStatus.PotentialRights.End.UTC().Format(time.RFC3339)) 436 if suggestedEnd.After(*licenseStatus.PotentialRights.End) { 437 msg := "Attempt to renew with a date greater than potential rights end = " + licenseStatus.PotentialRights.End.UTC().Format(time.RFC3339) 438 problem.Error(w, r, problem.Problem{Type: problem.RENEW_REJECT, Detail: msg}, http.StatusForbidden) 439 return 440 } 441 // check the suggested end date vs the current end date 442 if suggestedEnd.Before(currentEnd) { 443 msg := "Attempt to renew with a date before the current end date" 444 problem.Error(w, r, problem.Problem{Type: problem.RENEW_REJECT, Detail: msg}, http.StatusForbidden) 445 return 446 } 447 448 // add a log 449 logging.Print("Loan renewed until " + suggestedEnd.UTC().Format(time.RFC3339)) 450 451 // create a renew event 452 event := makeEvent(status.EVENT_RENEWED, deviceName, deviceID, licenseStatus.ID) 453 err = s.Transactions().Add(*event, status.EVENT_RENEWED_INT) 454 if err != nil { 455 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 456 return 457 } 458 459 // update a license via a call to the lcp Server 460 var httpStatusCode int 461 httpStatusCode, err = updateLicense(suggestedEnd, licenseID) 462 if err != nil { 463 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 464 return 465 } 466 if httpStatusCode != http.StatusOK && httpStatusCode != http.StatusPartialContent { // 200, 206 467 err = errors.New("LCP license PATCH returned HTTP error code " + strconv.Itoa(httpStatusCode)) 468 469 problem.Error(w, r, problem.Problem{Type: problem.REGISTRATION_BAD_REQUEST, Detail: err.Error()}, httpStatusCode) 470 return 471 } 472 // update the license status fields 473 licenseStatus.Status = status.STATUS_ACTIVE 474 licenseStatus.CurrentEndLicense = &suggestedEnd 475 licenseStatus.Updated.Status = &event.Timestamp 476 licenseStatus.Updated.License = &event.Timestamp 477 log.Print("Update timestamp ", event.Timestamp.UTC().Format(time.RFC3339)) 478 479 // update the license status in db 480 err = s.LicenseStatuses().Update(*licenseStatus) 481 if err != nil { 482 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 483 return 484 } 485 486 // fill the localized 'message', the 'links' and 'event' objects in the license status 487 err = fillLicenseStatus(licenseStatus, s) 488 if err != nil { 489 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 490 return 491 } 492 // return the updated license status to the caller 493 // the device count must not be sent in json to the caller 494 licenseStatus.DeviceCount = nil 495 enc := json.NewEncoder(w) 496 err = enc.Encode(licenseStatus) 497 if err != nil { 498 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 499 return 500 } 501 } 502 503 // FilterLicenseStatuses returns a sequence of license statuses, in their id order 504 // function for detecting licenses which used a lot of devices 505 func FilterLicenseStatuses(w http.ResponseWriter, r *http.Request, s Server) { 506 w.Header().Set("Content-Type", api.ContentType_JSON) 507 508 // Get request parameters. If not defined, set default values 509 rDevices := r.FormValue("devices") 510 if rDevices == "" { 511 rDevices = "0" 512 } 513 514 rPage := r.FormValue("page") 515 if rPage == "" { 516 rPage = "1" 517 } 518 519 rPerPage := r.FormValue("per_page") 520 if rPerPage == "" { 521 rPerPage = "10" 522 } 523 524 devicesLimit, err := strconv.ParseInt(rDevices, 10, 32) 525 if err != nil { 526 problem.Error(w, r, problem.Problem{Type: problem.FILTER_BAD_REQUEST, Detail: err.Error()}, http.StatusBadRequest) 527 return 528 } 529 530 page, err := strconv.ParseInt(rPage, 10, 32) 531 if err != nil { 532 problem.Error(w, r, problem.Problem{Type: problem.FILTER_BAD_REQUEST, Detail: err.Error()}, http.StatusBadRequest) 533 return 534 } 535 536 perPage, err := strconv.ParseInt(rPerPage, 10, 32) 537 if err != nil { 538 problem.Error(w, r, problem.Problem{Type: problem.FILTER_BAD_REQUEST, Detail: err.Error()}, http.StatusBadRequest) 539 return 540 } 541 542 if (page < 1) || (perPage < 1) || (devicesLimit < 0) { 543 problem.Error(w, r, problem.Problem{Type: problem.FILTER_BAD_REQUEST, Detail: "Devices, page, per_page must be positive number"}, http.StatusBadRequest) 544 return 545 } 546 547 page-- 548 549 licenseStatuses := make([]licensestatuses.LicenseStatus, 0) 550 551 fn := s.LicenseStatuses().List(devicesLimit, perPage, page*perPage) 552 for it, err := fn(); err == nil; it, err = fn() { 553 licenseStatuses = append(licenseStatuses, it) 554 } 555 556 devices := strconv.Itoa(int(devicesLimit)) 557 lsperpage := strconv.Itoa(int(perPage) + 1) 558 var resultLink string 559 560 if len(licenseStatuses) > 0 { 561 nextPage := strconv.Itoa(int(page) + 1) 562 resultLink += "</licenses/?devices=" + devices + "&page=" + nextPage + "&per_page=" + lsperpage + ">; rel=\"next\"; title=\"next\"" 563 } 564 565 if page > 0 { 566 previousPage := strconv.Itoa(int(page) - 1) 567 if len(resultLink) > 0 { 568 resultLink += ", " 569 } 570 resultLink += "</licenses/?devices=" + devices + "&page=" + previousPage + "&per_page=" + lsperpage + ">; rel=\"previous\"; title=\"previous\"" 571 } 572 573 if len(resultLink) > 0 { 574 w.Header().Set("Link", resultLink) 575 } 576 577 enc := json.NewEncoder(w) 578 err = enc.Encode(licenseStatuses) 579 if err != nil { 580 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 581 return 582 } 583 } 584 585 // ListRegisteredDevices returns data about the use of a given license 586 func ListRegisteredDevices(w http.ResponseWriter, r *http.Request, s Server) { 587 w.Header().Set("Content-Type", api.ContentType_JSON) 588 589 vars := mux.Vars(r) 590 licenseID := vars["key"] 591 592 licenseStatus, err := s.LicenseStatuses().GetByLicenseID(licenseID) 593 if err != nil { 594 if licenseStatus == nil { 595 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 596 return 597 } 598 599 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 600 return 601 } 602 603 registeredDevicesList := transactions.RegisteredDevicesList{Devices: make([]transactions.Device, 0), ID: licenseStatus.LicenseRef} 604 605 fn := s.Transactions().ListRegisteredDevices(licenseStatus.ID) 606 for it, err := fn(); err == nil; it, err = fn() { 607 registeredDevicesList.Devices = append(registeredDevicesList.Devices, it) 608 } 609 610 enc := json.NewEncoder(w) 611 err = enc.Encode(registeredDevicesList) 612 if err != nil { 613 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 614 return 615 } 616 } 617 618 // LendingCancellation cancels (before use) or revokes (after use) a license. 619 // parameters: 620 // 621 // key: license id 622 // partial license status: the new status and a message indicating why the status is being changed 623 // The new status can be either STATUS_CANCELLED or STATUS_REVOKED 624 func LendingCancellation(w http.ResponseWriter, r *http.Request, s Server) { 625 // get the license id 626 vars := mux.Vars(r) 627 licenseID := vars["key"] 628 629 logging.Print("Revoke or Cancel the License " + licenseID) 630 631 // get the current license status 632 licenseStatus, err := s.LicenseStatuses().GetByLicenseID(licenseID) 633 if err != nil { 634 // erroneous license id 635 if licenseStatus == nil { 636 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 637 return 638 } 639 // other error 640 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 641 return 642 } 643 // get the partial license status document 644 var newStatus licensestatuses.LicenseStatus 645 err = decodeJsonLicenseStatus(r, &newStatus) 646 if err != nil { 647 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 648 return 649 } 650 // the new status must be either cancelled or revoked 651 if newStatus.Status != status.STATUS_REVOKED && newStatus.Status != status.STATUS_CANCELLED { 652 msg := "The new status must be either cancelled or revoked" 653 problem.Error(w, r, problem.Problem{Type: problem.RETURN_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 654 return 655 } 656 657 // cancelling is only possible when the status is ready 658 if newStatus.Status == status.STATUS_CANCELLED && licenseStatus.Status != status.STATUS_READY { 659 msg := "The license is not on ready state, it can't be cancelled" 660 problem.Error(w, r, problem.Problem{Type: problem.RETURN_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 661 return 662 } 663 // revocation is only possible when the status is ready or active 664 if newStatus.Status == status.STATUS_REVOKED && licenseStatus.Status != status.STATUS_READY && licenseStatus.Status != status.STATUS_ACTIVE { 665 msg := "The license is not on ready or active state, it can't be revoked" 666 problem.Error(w, r, problem.Problem{Type: problem.RETURN_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 667 return 668 } 669 670 // override the new status, revoked -> cancelled, if the current status is ready 671 if newStatus.Status == status.STATUS_REVOKED && licenseStatus.Status == status.STATUS_READY { 672 newStatus.Status = status.STATUS_CANCELLED 673 } 674 675 // the new expiration time is now 676 currentTime := time.Now().UTC().Truncate(time.Second) 677 678 // update the license with the new expiration time, via a call to the lcp Server 679 httpStatusCode, erru := updateLicense(currentTime, licenseID) 680 if erru != nil { 681 problem.Error(w, r, problem.Problem{Detail: erru.Error()}, http.StatusInternalServerError) 682 return 683 } 684 if httpStatusCode != http.StatusOK && httpStatusCode != http.StatusPartialContent { // 200, 206 685 err = errors.New("License update notif to lcp server failed with http code " + strconv.Itoa(httpStatusCode)) 686 problem.Error(w, r, problem.Problem{Type: problem.SERVER_INTERNAL_ERROR, Detail: err.Error()}, httpStatusCode) 687 return 688 } 689 // create a cancel or revoke event 690 var st string 691 var ty int 692 if newStatus.Status == status.STATUS_CANCELLED { 693 st = status.STATUS_CANCELLED 694 ty = status.STATUS_CANCELLED_INT 695 } else { 696 st = status.STATUS_REVOKED 697 ty = status.STATUS_REVOKED_INT 698 } 699 // the event source is not a device. 700 deviceName := "system" 701 deviceID := "system" 702 event := makeEvent(st, deviceName, deviceID, licenseStatus.ID) 703 err = s.Transactions().Add(*event, ty) 704 if err != nil { 705 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 706 return 707 } 708 // update the license status properties with the new status & expiration item (now) 709 // the potential end timestamp is also removed. 710 licenseStatus.Status = newStatus.Status 711 licenseStatus.CurrentEndLicense = ¤tTime 712 licenseStatus.Updated.Status = ¤tTime 713 licenseStatus.Updated.License = ¤tTime 714 licenseStatus.PotentialRights = nil 715 716 // update the license status in db 717 err = s.LicenseStatuses().Update(*licenseStatus) 718 if err != nil { 719 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 720 return 721 } 722 } 723 724 // makeLicenseStatus sets fields of license status according to the config file 725 // and creates needed inner objects of license status 726 func makeLicenseStatus(license license.License, ls *licensestatuses.LicenseStatus) { 727 ls.LicenseRef = license.ID 728 729 registerAvailable := config.Config.LicenseStatus.Register 730 731 if license.Rights == nil || license.Rights.End == nil { 732 // The publication was purchased (not a loan), so we do not set LSD.PotentialRights.End 733 ls.CurrentEndLicense = nil 734 } else { 735 // license.Rights.End exists => this is a loan 736 endFromLicense := license.Rights.End.Add(0) 737 ls.CurrentEndLicense = &endFromLicense 738 ls.PotentialRights = new(licensestatuses.PotentialRights) 739 740 rentingDays := config.Config.LicenseStatus.RentingDays 741 if rentingDays > 0 { 742 endFromConfig := license.Issued.Add(time.Hour * 24 * time.Duration(rentingDays)) 743 744 if endFromLicense.After(endFromConfig) { 745 ls.PotentialRights.End = &endFromLicense 746 } else { 747 ls.PotentialRights.End = &endFromConfig 748 } 749 } else { 750 ls.PotentialRights.End = &endFromLicense 751 } 752 } 753 754 if registerAvailable { 755 ls.Status = status.STATUS_READY 756 } else { 757 ls.Status = status.STATUS_ACTIVE 758 } 759 760 ls.Updated = new(licensestatuses.Updated) 761 ls.Updated.License = &license.Issued 762 763 currentTime := time.Now().UTC().Truncate(time.Second) 764 ls.Updated.Status = ¤tTime 765 766 count := 0 767 ls.DeviceCount = &count 768 } 769 770 // getEvents gets the events from database for the license status 771 func getEvents(ls *licensestatuses.LicenseStatus, s Server) error { 772 events := make([]transactions.Event, 0) 773 774 fn := s.Transactions().GetByLicenseStatusId(ls.ID) 775 var err error 776 var event transactions.Event 777 for event, err = fn(); err == nil; event, err = fn() { 778 events = append(events, event) 779 } 780 781 if err == transactions.ErrNotFound { 782 ls.Events = events 783 err = nil 784 } 785 786 return err 787 } 788 789 // makeLinks creates and adds links to the license status 790 func makeLinks(ls *licensestatuses.LicenseStatus) { 791 lsdBaseURL := config.Config.LsdServer.PublicBaseUrl 792 licenseLinkURL := config.Config.LsdServer.LicenseLinkUrl 793 lcpBaseURL := config.Config.LcpServer.PublicBaseUrl 794 795 usableLicense := (ls.Status == status.STATUS_READY || ls.Status == status.STATUS_ACTIVE) 796 registerAvailable := config.Config.LicenseStatus.Register && usableLicense 797 licenseHasRightsEnd := ls.CurrentEndLicense != nil && !(*ls.CurrentEndLicense).IsZero() 798 returnAvailable := config.Config.LicenseStatus.Return && licenseHasRightsEnd && usableLicense 799 renewAvailable := config.Config.LicenseStatus.Renew && licenseHasRightsEnd && usableLicense 800 renewPageUrl := config.Config.LicenseStatus.RenewPageUrl 801 renewCustomUrl := config.Config.LicenseStatus.RenewCustomUrl 802 803 links := new([]licensestatuses.Link) 804 805 // if the link template to the license is set 806 if licenseLinkURL != "" { 807 licenseLinkURLFinal := expandUriTemplate(licenseLinkURL, "license_id", ls.LicenseRef) 808 link := licensestatuses.Link{Href: licenseLinkURLFinal, Rel: "license", Type: api.ContentType_LCP_JSON, Templated: false} 809 *links = append(*links, link) 810 // default template 811 } else { 812 link := licensestatuses.Link{Href: lcpBaseURL + "/api/v1/licenses/" + ls.LicenseRef, Rel: "license", Type: api.ContentType_LCP_JSON, Templated: false} 813 *links = append(*links, link) 814 } 815 // if register is set 816 if registerAvailable { 817 link := licensestatuses.Link{Href: lsdBaseURL + "/licenses/" + ls.LicenseRef + "/register{?id,name}", Rel: "register", Type: api.ContentType_LSD_JSON, Templated: true} 818 *links = append(*links, link) 819 } 820 // if return is set 821 if returnAvailable { 822 link := licensestatuses.Link{Href: lsdBaseURL + "/licenses/" + ls.LicenseRef + "/return{?id,name}", Rel: "return", Type: api.ContentType_LSD_JSON, Templated: true} 823 *links = append(*links, link) 824 } 825 826 // if renew is set 827 if renewAvailable { 828 var link licensestatuses.Link 829 if renewPageUrl != "" { 830 // renewal is managed via a web page 831 expandedUrl := expandUriTemplate(renewPageUrl, "license_id", ls.LicenseRef) 832 link = licensestatuses.Link{Href: expandedUrl, Rel: "renew", Type: api.ContentType_TEXT_HTML} 833 } else if renewCustomUrl != "" { 834 // renewal is managed via a specific service handled by the provider. 835 // The expanded renew url is itself a templated Url, which may of may not contain query parameters. 836 // Warning: {&end,id,name} (note the '&') may not be properly processed by most clients. 837 expandedUrl := expandUriTemplate(renewCustomUrl, "license_id", ls.LicenseRef) 838 if strings.Contains(renewCustomUrl, "?") { 839 expandedUrl = expandedUrl + "{&end,id,name}" 840 } else { 841 expandedUrl = expandedUrl + "{?end,id,name}" 842 } 843 link = licensestatuses.Link{Href: expandedUrl, Rel: "renew", Type: api.ContentType_LSD_JSON, Templated: true} 844 } else { 845 // this is the most usual case, i.e. a simple renew link 846 link = licensestatuses.Link{Href: lsdBaseURL + "/licenses/" + ls.LicenseRef + "/renew{?end,id,name}", Rel: "renew", Type: api.ContentType_LSD_JSON, Templated: true} 847 } 848 *links = append(*links, link) 849 } 850 851 ls.Links = *links 852 } 853 854 // expandUriTemplate resolves a url template from the configuration to a url the system can embed in a status document 855 func expandUriTemplate(uriTemplate, variable, value string) string { 856 template, _ := uritemplates.Parse(uriTemplate) 857 values := make(map[string]interface{}) 858 values[variable] = value 859 expanded, err := template.Expand(values) 860 if err != nil { 861 log.Printf("failed to expand an uri template: %s", uriTemplate) 862 return uriTemplate 863 } 864 return expanded 865 } 866 867 // makeEvent creates an event and fill it 868 func makeEvent(status string, deviceName string, deviceID string, licenseStatusFk int) *transactions.Event { 869 event := transactions.Event{} 870 event.DeviceId = deviceID 871 event.DeviceName = deviceName 872 event.Timestamp = time.Now().UTC().Truncate(time.Second) 873 event.Type = status 874 event.LicenseStatusFk = licenseStatusFk 875 876 return &event 877 } 878 879 // decodeJsonLicenseStatus decodes license status json to the object 880 func decodeJsonLicenseStatus(r *http.Request, ls *licensestatuses.LicenseStatus) error { 881 var dec *json.Decoder 882 883 if ctype := r.Header["Content-Type"]; len(ctype) > 0 && ctype[0] == api.ContentType_FORM_URL_ENCODED { 884 buf := bytes.NewBufferString(r.PostFormValue("data")) 885 dec = json.NewDecoder(buf) 886 } else { 887 dec = json.NewDecoder(r.Body) 888 } 889 890 err := dec.Decode(&ls) 891 892 return err 893 } 894 895 // updateLicense updates a license by calling the License Server 896 // called from return, renew and cancel/revoke actions 897 func updateLicense(timeEnd time.Time, licenseID string) (int, error) { 898 // get the lcp server url 899 lcpBaseURL := config.Config.LcpServer.PublicBaseUrl 900 if len(lcpBaseURL) <= 0 { 901 return 0, errors.New("undefined Config.LcpServer.PublicBaseUrl") 902 } 903 // create a minimum license object, limited to the license id plus rights 904 // FIXME: remove the id (here and in the lcpserver license.go) 905 minLicense := license.License{ID: licenseID, Rights: new(license.UserRights)} 906 // set the new end date 907 minLicense.Rights.End = &timeEnd 908 909 var lcpClient = &http.Client{ 910 Timeout: time.Second * 10, 911 } 912 // FIXME: this Pipe thing should be replaced by a json.Marshal 913 pr, pw := io.Pipe() 914 go func() { 915 _ = json.NewEncoder(pw).Encode(minLicense) 916 pw.Close() 917 }() 918 // prepare the request 919 lcpURL := lcpBaseURL + "/licenses/" + licenseID 920 // message to the console 921 //log.Println("PATCH " + lcpURL) 922 // send the content to the LCP server 923 req, err := http.NewRequest("PATCH", lcpURL, pr) 924 if err != nil { 925 return 0, err 926 } 927 // set the credentials 928 updateAuth := config.Config.LcpUpdateAuth 929 if updateAuth.Username != "" { 930 req.SetBasicAuth(updateAuth.Username, updateAuth.Password) 931 } 932 // set the content type 933 req.Header.Add("Content-Type", api.ContentType_LCP_JSON) 934 // send the request to the lcp server 935 response, err := lcpClient.Do(req) 936 if err == nil { 937 if response.StatusCode != http.StatusOK { 938 log.Println("Notify Lcp Server of License (" + licenseID + ") = " + strconv.Itoa(response.StatusCode)) 939 } 940 return response.StatusCode, nil 941 } 942 943 log.Println("Error Notifying Lcp Server of License update (" + licenseID + "):" + err.Error()) 944 return 0, err 945 } 946 947 // fillLicenseStatus fills the 'message' field, the 'links' and 'event' objects in the license status 948 func fillLicenseStatus(ls *licensestatuses.LicenseStatus, s Server) error { 949 // add the message 950 ls.Message = "The license is in " + ls.Status + " state" 951 // add the links 952 makeLinks(ls) 953 // add the events 954 err := getEvents(ls, s) 955 956 return err 957 }