github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/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, r, 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, r, 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 322 err = s.LicenseStatuses().Update(*licenseStatus) 323 if err != nil { 324 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 325 return 326 } 327 328 // fill the license status 329 err = fillLicenseStatus(licenseStatus, r, s) 330 if err != nil { 331 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 332 return 333 } 334 335 // the device count must not be sent in json to the caller 336 licenseStatus.DeviceCount = nil 337 enc := json.NewEncoder(w) 338 err = enc.Encode(licenseStatus) 339 340 if err != nil { 341 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 342 return 343 } 344 } 345 346 // LendingRenewal checks that the calling device is registered with the license, 347 // then modifies the end date associated with the license 348 // and returns an updated license status to the caller. 349 // the 'end' parameter is optional; if absent, the end date is computed from 350 // the current end date plus a configuration parameter. 351 // Note: as per the spec, a non-registered device can renew a loan. 352 func LendingRenewal(w http.ResponseWriter, r *http.Request, s Server) { 353 w.Header().Set("Content-Type", api.ContentType_LSD_JSON) 354 vars := mux.Vars(r) 355 356 var msg string 357 358 // get the license status by license id 359 licenseID := vars["key"] 360 361 deviceID := r.FormValue("id") 362 deviceName := r.FormValue("name") 363 364 // add a log 365 logging.Print("Renew the Loan from Device " + deviceName + " with id " + deviceID + " for License " + licenseID) 366 367 // check the request parameters 368 if (len(deviceName) > 255) || (len(deviceID) > 255) { 369 msg = "device id and device name are mandatory and their maximum length is 255 bytes" 370 problem.Error(w, r, problem.Problem{Type: problem.RENEW_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 371 return 372 } 373 374 // get the license status 375 licenseStatus, err := s.LicenseStatuses().GetByLicenseID(licenseID) 376 if err != nil { 377 if licenseStatus == nil { 378 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 379 return 380 } 381 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 382 return 383 } 384 385 // check that the license status is active. 386 // note: renewing an unactive (ready) license is forbidden 387 if licenseStatus.Status != status.STATUS_ACTIVE { 388 msg = "The current license status is " + licenseStatus.Status + "; renew forbidden" 389 problem.Error(w, r, problem.Problem{Type: problem.RENEW_BAD_REQUEST, Detail: msg}, http.StatusForbidden) 390 return 391 } 392 393 // check if the license contains a date end property 394 var currentEnd time.Time 395 if licenseStatus.CurrentEndLicense == nil || (*licenseStatus.CurrentEndLicense).IsZero() { 396 msg = "This license has no current end date; it cannot be renewed" 397 problem.Error(w, r, problem.Problem{Type: problem.RENEW_BAD_REQUEST, Detail: msg}, http.StatusForbidden) 398 return 399 } 400 currentEnd = *licenseStatus.CurrentEndLicense 401 //log.Print("Lending renewal activated. Current end date " + currentEnd.UTC().Format(time.RFC3339)) 402 403 var suggestedEnd time.Time 404 // check if the 'end' request parameter is empty 405 timeEndString := r.FormValue("end") 406 if timeEndString == "" { 407 // get the config parameter renew_days 408 renewDays := config.Config.LicenseStatus.RenewDays 409 if renewDays == 0 { 410 msg = "No explicit end value and no configured value" 411 problem.Error(w, r, problem.Problem{Detail: msg}, http.StatusInternalServerError) 412 return 413 } 414 // compute a suggested duration from the config value 415 suggestedDuration := 24 * time.Hour * time.Duration(renewDays) // nanoseconds 416 417 // compute the suggested end date from the current end date 418 suggestedEnd = currentEnd.Add(time.Duration(suggestedDuration)) 419 //log.Print("Default extension request until ", suggestedEnd.UTC().Format(time.RFC3339)) 420 421 // if the 'end' request parameter is set 422 } else { 423 var err error 424 suggestedEnd, err = time.Parse(time.RFC3339, timeEndString) 425 if err != nil { 426 problem.Error(w, r, problem.Problem{Type: problem.RENEW_BAD_REQUEST, Detail: err.Error()}, http.StatusBadRequest) 427 return 428 } 429 //log.Print("Explicit extension request until ", suggestedEnd.UTC().Format(time.RFC3339)) 430 } 431 432 // check the suggested end date vs the upper end date (which is already set in our implementation) 433 //log.Print("Potential rights end = ", licenseStatus.PotentialRights.End.UTC().Format(time.RFC3339)) 434 if suggestedEnd.After(*licenseStatus.PotentialRights.End) { 435 msg := "Attempt to renew with a date greater than potential rights end = " + licenseStatus.PotentialRights.End.UTC().Format(time.RFC3339) 436 problem.Error(w, r, problem.Problem{Type: problem.RENEW_REJECT, Detail: msg}, http.StatusForbidden) 437 return 438 } 439 // check the suggested end date vs the current end date 440 if suggestedEnd.Before(currentEnd) { 441 msg := "Attempt to renew with a date before the current end date" 442 problem.Error(w, r, problem.Problem{Type: problem.RENEW_REJECT, Detail: msg}, http.StatusForbidden) 443 return 444 } 445 446 // add a log 447 logging.Print("Loan renewed until " + suggestedEnd.UTC().Format(time.RFC3339)) 448 449 // create a renew event 450 event := makeEvent(status.EVENT_RENEWED, deviceName, deviceID, licenseStatus.ID) 451 err = s.Transactions().Add(*event, status.EVENT_RENEWED_INT) 452 if err != nil { 453 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 454 return 455 } 456 457 // update a license via a call to the lcp Server 458 httpStatusCode, errorr := updateLicense(suggestedEnd, licenseID) 459 if errorr != nil { 460 problem.Error(w, r, problem.Problem{Detail: errorr.Error()}, http.StatusInternalServerError) 461 return 462 } 463 if httpStatusCode != http.StatusOK && httpStatusCode != http.StatusPartialContent { // 200, 206 464 errorr = errors.New("LCP license PATCH returned HTTP error code " + strconv.Itoa(httpStatusCode)) 465 466 problem.Error(w, r, problem.Problem{Type: problem.REGISTRATION_BAD_REQUEST, Detail: errorr.Error()}, httpStatusCode) 467 return 468 } 469 // update the license status fields 470 licenseStatus.Status = status.STATUS_ACTIVE 471 licenseStatus.CurrentEndLicense = &suggestedEnd 472 licenseStatus.Updated.Status = &event.Timestamp 473 licenseStatus.Updated.License = &event.Timestamp 474 475 // update the license status in db 476 err = s.LicenseStatuses().Update(*licenseStatus) 477 if err != nil { 478 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 479 return 480 } 481 482 // fill the localized 'message', the 'links' and 'event' objects in the license status 483 err = fillLicenseStatus(licenseStatus, r, s) 484 if err != nil { 485 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 486 return 487 } 488 // return the updated license status to the caller 489 // the device count must not be sent in json to the caller 490 licenseStatus.DeviceCount = nil 491 enc := json.NewEncoder(w) 492 err = enc.Encode(licenseStatus) 493 if err != nil { 494 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 495 return 496 } 497 } 498 499 // FilterLicenseStatuses returns a sequence of license statuses, in their id order 500 // function for detecting licenses which used a lot of devices 501 func FilterLicenseStatuses(w http.ResponseWriter, r *http.Request, s Server) { 502 w.Header().Set("Content-Type", api.ContentType_JSON) 503 504 // Get request parameters. If not defined, set default values 505 rDevices := r.FormValue("devices") 506 if rDevices == "" { 507 rDevices = "0" 508 } 509 510 rPage := r.FormValue("page") 511 if rPage == "" { 512 rPage = "1" 513 } 514 515 rPerPage := r.FormValue("per_page") 516 if rPerPage == "" { 517 rPerPage = "10" 518 } 519 520 devicesLimit, err := strconv.ParseInt(rDevices, 10, 32) 521 if err != nil { 522 problem.Error(w, r, problem.Problem{Type: problem.FILTER_BAD_REQUEST, Detail: err.Error()}, http.StatusBadRequest) 523 return 524 } 525 526 page, err := strconv.ParseInt(rPage, 10, 32) 527 if err != nil { 528 problem.Error(w, r, problem.Problem{Type: problem.FILTER_BAD_REQUEST, Detail: err.Error()}, http.StatusBadRequest) 529 return 530 } 531 532 perPage, err := strconv.ParseInt(rPerPage, 10, 32) 533 if err != nil { 534 problem.Error(w, r, problem.Problem{Type: problem.FILTER_BAD_REQUEST, Detail: err.Error()}, http.StatusBadRequest) 535 return 536 } 537 538 if (page < 1) || (perPage < 1) || (devicesLimit < 0) { 539 problem.Error(w, r, problem.Problem{Type: problem.FILTER_BAD_REQUEST, Detail: "Devices, page, per_page must be positive number"}, http.StatusBadRequest) 540 return 541 } 542 543 page-- 544 545 licenseStatuses := make([]licensestatuses.LicenseStatus, 0) 546 547 fn := s.LicenseStatuses().List(devicesLimit, perPage, page*perPage) 548 for it, err := fn(); err == nil; it, err = fn() { 549 licenseStatuses = append(licenseStatuses, it) 550 } 551 552 devices := strconv.Itoa(int(devicesLimit)) 553 lsperpage := strconv.Itoa(int(perPage) + 1) 554 var resultLink string 555 556 if len(licenseStatuses) > 0 { 557 nextPage := strconv.Itoa(int(page) + 1) 558 resultLink += "</licenses/?devices=" + devices + "&page=" + nextPage + "&per_page=" + lsperpage + ">; rel=\"next\"; title=\"next\"" 559 } 560 561 if page > 0 { 562 previousPage := strconv.Itoa(int(page) - 1) 563 if len(resultLink) > 0 { 564 resultLink += ", " 565 } 566 resultLink += "</licenses/?devices=" + devices + "&page=" + previousPage + "&per_page=" + lsperpage + ">; rel=\"previous\"; title=\"previous\"" 567 } 568 569 if len(resultLink) > 0 { 570 w.Header().Set("Link", resultLink) 571 } 572 573 enc := json.NewEncoder(w) 574 err = enc.Encode(licenseStatuses) 575 if err != nil { 576 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 577 return 578 } 579 } 580 581 // ListRegisteredDevices returns data about the use of a given license 582 func ListRegisteredDevices(w http.ResponseWriter, r *http.Request, s Server) { 583 w.Header().Set("Content-Type", api.ContentType_JSON) 584 585 vars := mux.Vars(r) 586 licenseID := vars["key"] 587 588 licenseStatus, err := s.LicenseStatuses().GetByLicenseID(licenseID) 589 if err != nil { 590 if licenseStatus == nil { 591 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 592 return 593 } 594 595 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 596 return 597 } 598 599 registeredDevicesList := transactions.RegisteredDevicesList{Devices: make([]transactions.Device, 0), ID: licenseStatus.LicenseRef} 600 601 fn := s.Transactions().ListRegisteredDevices(licenseStatus.ID) 602 for it, err := fn(); err == nil; it, err = fn() { 603 registeredDevicesList.Devices = append(registeredDevicesList.Devices, it) 604 } 605 606 enc := json.NewEncoder(w) 607 err = enc.Encode(registeredDevicesList) 608 if err != nil { 609 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 610 return 611 } 612 } 613 614 // LendingCancellation cancels (before use) or revokes (after use) a license. 615 // parameters: 616 // 617 // key: license id 618 // partial license status: the new status and a message indicating why the status is being changed 619 // The new status can be either STATUS_CANCELLED or STATUS_REVOKED 620 func LendingCancellation(w http.ResponseWriter, r *http.Request, s Server) { 621 // get the license id 622 vars := mux.Vars(r) 623 licenseID := vars["key"] 624 625 logging.Print("Revoke or Cancel the License " + licenseID) 626 627 // get the current license status 628 licenseStatus, err := s.LicenseStatuses().GetByLicenseID(licenseID) 629 if err != nil { 630 // erroneous license id 631 if licenseStatus == nil { 632 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 633 return 634 } 635 // other error 636 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 637 return 638 } 639 // get the partial license status document 640 var newStatus licensestatuses.LicenseStatus 641 err = decodeJsonLicenseStatus(r, &newStatus) 642 if err != nil { 643 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 644 return 645 } 646 // the new status must be either cancelled or revoked 647 if newStatus.Status != status.STATUS_REVOKED && newStatus.Status != status.STATUS_CANCELLED { 648 msg := "The new status must be either cancelled or revoked" 649 problem.Error(w, r, problem.Problem{Type: problem.RETURN_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 650 return 651 } 652 653 // cancelling is only possible when the status is ready 654 if newStatus.Status == status.STATUS_CANCELLED && licenseStatus.Status != status.STATUS_READY { 655 msg := "The license is not on ready state, it can't be cancelled" 656 problem.Error(w, r, problem.Problem{Type: problem.RETURN_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 657 return 658 } 659 // revocation is only possible when the status is ready or active 660 if newStatus.Status == status.STATUS_REVOKED && licenseStatus.Status != status.STATUS_READY && licenseStatus.Status != status.STATUS_ACTIVE { 661 msg := "The license is not on ready or active state, it can't be revoked" 662 problem.Error(w, r, problem.Problem{Type: problem.RETURN_BAD_REQUEST, Detail: msg}, http.StatusBadRequest) 663 return 664 } 665 666 // override the new status, revoked -> cancelled, if the current status is ready 667 if newStatus.Status == status.STATUS_REVOKED && licenseStatus.Status == status.STATUS_READY { 668 newStatus.Status = status.STATUS_CANCELLED 669 } 670 671 // the new expiration time is now 672 currentTime := time.Now().UTC().Truncate(time.Second) 673 674 // update the license with the new expiration time, via a call to the lcp Server 675 httpStatusCode, erru := updateLicense(currentTime, licenseID) 676 if erru != nil { 677 problem.Error(w, r, problem.Problem{Detail: erru.Error()}, http.StatusInternalServerError) 678 return 679 } 680 if httpStatusCode != http.StatusOK && httpStatusCode != http.StatusPartialContent { // 200, 206 681 err = errors.New("License update notif to lcp server failed with http code " + strconv.Itoa(httpStatusCode)) 682 problem.Error(w, r, problem.Problem{Type: problem.SERVER_INTERNAL_ERROR, Detail: err.Error()}, httpStatusCode) 683 return 684 } 685 // create a cancel or revoke event 686 var st string 687 var ty int 688 if newStatus.Status == status.STATUS_CANCELLED { 689 st = status.STATUS_CANCELLED 690 ty = status.STATUS_CANCELLED_INT 691 } else { 692 st = status.STATUS_REVOKED 693 ty = status.STATUS_REVOKED_INT 694 } 695 // the event source is not a device. 696 deviceName := "system" 697 deviceID := "system" 698 event := makeEvent(st, deviceName, deviceID, licenseStatus.ID) 699 err = s.Transactions().Add(*event, ty) 700 if err != nil { 701 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 702 return 703 } 704 // update the license status properties with the new status & expiration item (now) 705 licenseStatus.Status = newStatus.Status 706 licenseStatus.CurrentEndLicense = ¤tTime 707 licenseStatus.Updated.Status = ¤tTime 708 licenseStatus.Updated.License = ¤tTime 709 710 // update the license status in db 711 err = s.LicenseStatuses().Update(*licenseStatus) 712 if err != nil { 713 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 714 return 715 } 716 } 717 718 // makeLicenseStatus sets fields of license status according to the config file 719 // and creates needed inner objects of license status 720 func makeLicenseStatus(license license.License, ls *licensestatuses.LicenseStatus) { 721 ls.LicenseRef = license.ID 722 723 registerAvailable := config.Config.LicenseStatus.Register 724 725 if license.Rights == nil || license.Rights.End == nil { 726 // The publication was purchased (not a loan), so we do not set LSD.PotentialRights.End 727 ls.CurrentEndLicense = nil 728 } else { 729 // license.Rights.End exists => this is a loan 730 endFromLicense := license.Rights.End.Add(0) 731 ls.CurrentEndLicense = &endFromLicense 732 ls.PotentialRights = new(licensestatuses.PotentialRights) 733 734 rentingDays := config.Config.LicenseStatus.RentingDays 735 if rentingDays > 0 { 736 endFromConfig := license.Issued.Add(time.Hour * 24 * time.Duration(rentingDays)) 737 738 if endFromLicense.After(endFromConfig) { 739 ls.PotentialRights.End = &endFromLicense 740 } else { 741 ls.PotentialRights.End = &endFromConfig 742 } 743 } else { 744 ls.PotentialRights.End = &endFromLicense 745 } 746 } 747 748 if registerAvailable { 749 ls.Status = status.STATUS_READY 750 } else { 751 ls.Status = status.STATUS_ACTIVE 752 } 753 754 ls.Updated = new(licensestatuses.Updated) 755 ls.Updated.License = &license.Issued 756 757 currentTime := time.Now().UTC().Truncate(time.Second) 758 ls.Updated.Status = ¤tTime 759 760 count := 0 761 ls.DeviceCount = &count 762 } 763 764 // getEvents gets the events from database for the license status 765 func getEvents(ls *licensestatuses.LicenseStatus, s Server) error { 766 events := make([]transactions.Event, 0) 767 768 fn := s.Transactions().GetByLicenseStatusId(ls.ID) 769 var err error 770 var event transactions.Event 771 for event, err = fn(); err == nil; event, err = fn() { 772 events = append(events, event) 773 } 774 775 if err == transactions.ErrNotFound { 776 ls.Events = events 777 err = nil 778 } 779 780 return err 781 } 782 783 // makeLinks creates and adds links to the license status 784 func makeLinks(ls *licensestatuses.LicenseStatus) { 785 lsdBaseURL := config.Config.LsdServer.PublicBaseUrl 786 licenseLinkURL := config.Config.LsdServer.LicenseLinkUrl 787 lcpBaseURL := config.Config.LcpServer.PublicBaseUrl 788 789 usableLicense := (ls.Status == status.STATUS_READY || ls.Status == status.STATUS_ACTIVE) 790 registerAvailable := config.Config.LicenseStatus.Register && usableLicense 791 licenseHasRightsEnd := ls.CurrentEndLicense != nil && !(*ls.CurrentEndLicense).IsZero() 792 returnAvailable := config.Config.LicenseStatus.Return && licenseHasRightsEnd && usableLicense 793 renewAvailable := config.Config.LicenseStatus.Renew && licenseHasRightsEnd && usableLicense 794 renewPageUrl := config.Config.LicenseStatus.RenewPageUrl 795 renewCustomUrl := config.Config.LicenseStatus.RenewCustomUrl 796 797 links := new([]licensestatuses.Link) 798 799 // if the link template to the license is set 800 if licenseLinkURL != "" { 801 licenseLinkURLFinal := expandUriTemplate(licenseLinkURL, "license_id", ls.LicenseRef) 802 link := licensestatuses.Link{Href: licenseLinkURLFinal, Rel: "license", Type: api.ContentType_LCP_JSON, Templated: false} 803 *links = append(*links, link) 804 // default template 805 } else { 806 link := licensestatuses.Link{Href: lcpBaseURL + "/api/v1/licenses/" + ls.LicenseRef, Rel: "license", Type: api.ContentType_LCP_JSON, Templated: false} 807 *links = append(*links, link) 808 } 809 // if register is set 810 if registerAvailable { 811 link := licensestatuses.Link{Href: lsdBaseURL + "/licenses/" + ls.LicenseRef + "/register{?id,name}", Rel: "register", Type: api.ContentType_LSD_JSON, Templated: true} 812 *links = append(*links, link) 813 } 814 // if return is set 815 if returnAvailable { 816 link := licensestatuses.Link{Href: lsdBaseURL + "/licenses/" + ls.LicenseRef + "/return{?id,name}", Rel: "return", Type: api.ContentType_LSD_JSON, Templated: true} 817 *links = append(*links, link) 818 } 819 820 // if renew is set 821 if renewAvailable { 822 var link licensestatuses.Link 823 if renewPageUrl != "" { 824 // renewal is managed via a web page 825 expandedUrl := expandUriTemplate(renewPageUrl, "license_id", ls.LicenseRef) 826 link = licensestatuses.Link{Href: expandedUrl, Rel: "renew", Type: api.ContentType_TEXT_HTML} 827 } else if renewCustomUrl != "" { 828 // renewal is managed via a specific service handled by the provider. 829 // The expanded renew url is itself a templated Url, which may of may not contain query parameters. 830 // Warning: {&end,id,name} (note the '&') may not be properly processed by most clients. 831 expandedUrl := expandUriTemplate(renewCustomUrl, "license_id", ls.LicenseRef) 832 if strings.Contains(renewCustomUrl, "?") { 833 expandedUrl = expandedUrl + "{&end,id,name}" 834 } else { 835 expandedUrl = expandedUrl + "{?end,id,name}" 836 } 837 link = licensestatuses.Link{Href: expandedUrl, Rel: "renew", Type: api.ContentType_LSD_JSON, Templated: true} 838 } else { 839 // this is the most usual case, i.e. a simple renew link 840 link = licensestatuses.Link{Href: lsdBaseURL + "/licenses/" + ls.LicenseRef + "/renew{?end,id,name}", Rel: "renew", Type: api.ContentType_LSD_JSON, Templated: true} 841 } 842 *links = append(*links, link) 843 } 844 845 ls.Links = *links 846 } 847 848 // expandUriTemplate resolves a url template from the configuration to a url the system can embed in a status document 849 func expandUriTemplate(uriTemplate, variable, value string) string { 850 template, _ := uritemplates.Parse(uriTemplate) 851 values := make(map[string]interface{}) 852 values[variable] = value 853 expanded, err := template.Expand(values) 854 if err != nil { 855 log.Printf("failed to expand an uri template: %s", uriTemplate) 856 return uriTemplate 857 } 858 return expanded 859 } 860 861 // makeEvent creates an event and fill it 862 func makeEvent(status string, deviceName string, deviceID string, licenseStatusFk int) *transactions.Event { 863 event := transactions.Event{} 864 event.DeviceId = deviceID 865 event.DeviceName = deviceName 866 event.Timestamp = time.Now().UTC().Truncate(time.Second) 867 event.Type = status 868 event.LicenseStatusFk = licenseStatusFk 869 870 return &event 871 } 872 873 // decodeJsonLicenseStatus decodes license status json to the object 874 func decodeJsonLicenseStatus(r *http.Request, ls *licensestatuses.LicenseStatus) error { 875 var dec *json.Decoder 876 877 if ctype := r.Header["Content-Type"]; len(ctype) > 0 && ctype[0] == api.ContentType_FORM_URL_ENCODED { 878 buf := bytes.NewBufferString(r.PostFormValue("data")) 879 dec = json.NewDecoder(buf) 880 } else { 881 dec = json.NewDecoder(r.Body) 882 } 883 884 err := dec.Decode(&ls) 885 886 return err 887 } 888 889 // updateLicense updates a license by calling the License Server 890 // called from return, renew and cancel/revoke actions 891 func updateLicense(timeEnd time.Time, licenseID string) (int, error) { 892 // get the lcp server url 893 lcpBaseURL := config.Config.LcpServer.PublicBaseUrl 894 if len(lcpBaseURL) <= 0 { 895 return 0, errors.New("undefined Config.LcpServer.PublicBaseUrl") 896 } 897 // create a minimum license object, limited to the license id plus rights 898 // FIXME: remove the id (here and in the lcpserver license.go) 899 minLicense := license.License{ID: licenseID, Rights: new(license.UserRights)} 900 // set the new end date 901 minLicense.Rights.End = &timeEnd 902 903 var lcpClient = &http.Client{ 904 Timeout: time.Second * 10, 905 } 906 // FIXME: this Pipe thing should be replaced by a json.Marshal 907 pr, pw := io.Pipe() 908 go func() { 909 _ = json.NewEncoder(pw).Encode(minLicense) 910 pw.Close() 911 }() 912 // prepare the request 913 lcpURL := lcpBaseURL + "/licenses/" + licenseID 914 // message to the console 915 //log.Println("PATCH " + lcpURL) 916 // send the content to the LCP server 917 req, err := http.NewRequest("PATCH", lcpURL, pr) 918 if err != nil { 919 return 0, err 920 } 921 // set the credentials 922 updateAuth := config.Config.LcpUpdateAuth 923 if updateAuth.Username != "" { 924 req.SetBasicAuth(updateAuth.Username, updateAuth.Password) 925 } 926 // set the content type 927 req.Header.Add("Content-Type", api.ContentType_LCP_JSON) 928 // send the request to the lcp server 929 response, err := lcpClient.Do(req) 930 if err == nil { 931 if response.StatusCode != http.StatusOK { 932 log.Println("Notify Lcp Server of License (" + licenseID + ") = " + strconv.Itoa(response.StatusCode)) 933 } 934 return response.StatusCode, nil 935 } 936 937 log.Println("Error Notifying Lcp Server of License update (" + licenseID + "):" + err.Error()) 938 return 0, err 939 } 940 941 // fillLicenseStatus fills the 'message' field, the 'links' and 'event' objects in the license status 942 func fillLicenseStatus(ls *licensestatuses.LicenseStatus, r *http.Request, s Server) error { 943 // add the message 944 ls.Message = "The license is in " + ls.Status + " state" 945 // add the links 946 makeLinks(ls) 947 // add the events 948 err := getEvents(ls, s) 949 950 return err 951 }