github.com/readium/readium-lcp-server@v0.0.0-20240509124024-799e77a0bbd6/lcpserver/api/license.go (about) 1 // Copyright 2017 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 apilcp 6 7 import ( 8 "archive/zip" 9 "bytes" 10 "encoding/hex" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "io" 15 "log" 16 "net/http" 17 "strconv" 18 "time" 19 20 "github.com/gorilla/mux" 21 22 "github.com/readium/readium-lcp-server/api" 23 "github.com/readium/readium-lcp-server/config" 24 "github.com/readium/readium-lcp-server/epub" 25 "github.com/readium/readium-lcp-server/index" 26 "github.com/readium/readium-lcp-server/license" 27 "github.com/readium/readium-lcp-server/logging" 28 "github.com/readium/readium-lcp-server/problem" 29 "github.com/readium/readium-lcp-server/storage" 30 ) 31 32 // ErrMandatoryInfoMissing sets an error message returned to the caller 33 var ErrMandatoryInfoMissing = errors.New("mandatory info missing in the input body") 34 35 // ErrBadHexValue sets an error message returned to the caller 36 var ErrBadHexValue = errors.New("erroneous user_key.hex_value can't be decoded") 37 38 // ErrBadValue sets an error message returned to the caller 39 var ErrBadValue = errors.New("erroneous user_key.value, can't be decoded") 40 41 // lastGeneratedLicense is for tests only 42 var lastGeneratedLicense license.License 43 44 // checkGetLicenseInput: if we generate or get a license, check mandatory information in the input body 45 // and compute request parameters 46 func checkGetLicenseInput(l *license.License) error { 47 48 // the user hint is mandatory 49 if l.Encryption.UserKey.Hint == "" { 50 log.Println("User hint is missing") 51 return ErrMandatoryInfoMissing 52 } 53 // Value or HexValue are mandatory 54 // HexValue (hex encoded passphrase hash) takes precedence over Value (kept for backward compatibility) 55 // Value is computed from HexValue if set 56 if l.Encryption.UserKey.HexValue != "" { 57 // compute a byte array from a string 58 value, err := hex.DecodeString(l.Encryption.UserKey.HexValue) 59 if err != nil { 60 return ErrBadHexValue 61 } 62 l.Encryption.UserKey.Value = value 63 } else if l.Encryption.UserKey.Value == nil { 64 log.Println("User hashed passphrase is missing") 65 return ErrMandatoryInfoMissing 66 } 67 // check the size of Value (32 bytes), to avoid weird errors in the crypto code 68 if len(l.Encryption.UserKey.Value) != 32 { 69 return ErrBadValue 70 } 71 72 return nil 73 } 74 75 // checkGenerateLicenseInput: if we generate a license, check mandatory information in the input body 76 func checkGenerateLicenseInput(l *license.License) error { 77 78 if l.User.ID == "" { 79 log.Println("User identification is missing") 80 return ErrMandatoryInfoMissing 81 } 82 // check user hint, passphrase hash and hash algorithm 83 err := checkGetLicenseInput(l) 84 return err 85 } 86 87 // get license, copy useful data from licIn to LicOut 88 func copyInputToLicense(licIn *license.License, licOut *license.License) { 89 90 // copy the user hint and hashed passphrase 91 licOut.Encryption.UserKey.Hint = licIn.Encryption.UserKey.Hint 92 licOut.Encryption.UserKey.Value = licIn.Encryption.UserKey.Value 93 licOut.Encryption.UserKey.HexValue = licIn.Encryption.UserKey.HexValue 94 95 // copy optional user information 96 licOut.User.Email = licIn.User.Email 97 licOut.User.Name = licIn.User.Name 98 licOut.User.Encrypted = licIn.User.Encrypted 99 licOut.Links = licIn.Links 100 } 101 102 // normalize the start and end date, UTC, no milliseconds 103 func setRights(lic *license.License) { 104 105 // a rights object is needed before adding a record to the db 106 if lic.Rights == nil { 107 lic.Rights = new(license.UserRights) 108 } 109 if lic.Rights.Start != nil { 110 start := lic.Rights.Start.UTC().Truncate(time.Second) 111 lic.Rights.Start = &start 112 } 113 if lic.Rights.End != nil { 114 end := lic.Rights.End.UTC().Truncate(time.Second) 115 lic.Rights.End = &end 116 } 117 } 118 119 // build a license, common to get and generate license, get and generate a protected publication 120 func buildLicense(lic *license.License, s Server, updatefix bool) error { 121 122 // set the LCP profile 123 err := license.SetLicenseProfile(lic) 124 if err != nil { 125 log.Println("Build License: " + err.Error()) 126 return err 127 } 128 129 // force the algorithm to the one defined by the current profiles 130 lic.Encryption.UserKey.Algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" 131 132 // get content info from the db 133 content, err := s.Index().Get(lic.ContentID) 134 if err != nil { 135 log.Println("No content with id", lic.ContentID) 136 return err 137 } 138 139 // set links 140 err = license.SetLicenseLinks(lic, content) 141 if err != nil { 142 return err 143 } 144 // encrypt the content key, user fieds, set the key check 145 err = license.EncryptLicenseFields(lic, content) 146 if err != nil { 147 return err 148 } 149 150 // fix an issue with clients which test that the date of last update of the license 151 // is after the date of creation of the X509 certificate. 152 // Because of this, when a cert is replaced, fresh licenses are not accepted by such clients 153 // when they have been created / updated before the cert update. 154 if updatefix && config.Config.LcpServer.CertDate != "" { 155 certDate, err := time.Parse("2006-01-02", config.Config.LcpServer.CertDate) 156 if err != nil { 157 return err 158 } 159 if lic.Issued.Before(certDate) && (lic.Updated == nil || lic.Updated.Before(certDate)) { 160 lic.Updated = &certDate 161 } 162 } 163 164 // sign the license 165 err = license.SignLicense(lic, s.Certificate()) 166 if err != nil { 167 return err 168 } 169 return nil 170 } 171 172 // copyZipFiles copies every file from one zip archive to another 173 func copyZipFiles(out *zip.Writer, in *zip.Reader) error { 174 175 for _, file := range in.File { 176 newFile, err := out.CreateHeader(&file.FileHeader) 177 if err != nil { 178 return err 179 } 180 181 r, err := file.Open() 182 if err != nil { 183 return err 184 } 185 186 _, err = io.Copy(newFile, r) 187 r.Close() 188 if err != nil { 189 return err 190 } 191 } 192 193 return nil 194 } 195 196 // isWebPub checks the presence of a REadium manifest is a zip package 197 func isWebPub(in *zip.Reader) bool { 198 199 for _, f := range in.File { 200 if f.Name == "manifest.json" { 201 return true 202 } 203 } 204 205 return false 206 } 207 208 // buildProtectedPublication builds a protected publication, common to get and generate protected publication 209 func buildProtectedPublication(lic *license.License, s Server) (buf bytes.Buffer, err error) { 210 211 // get content info from the bd 212 item, err := s.Store().Get(lic.ContentID) 213 if err != nil { 214 return 215 } 216 // read the content into a buffer 217 contents, err := item.Contents() 218 if err != nil { 219 return buf, err 220 } 221 b, err := io.ReadAll(contents) 222 if err != nil { 223 return buf, err 224 } 225 // create a zip reader 226 zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) 227 if err != nil { 228 return buf, err 229 } 230 231 zipWriter := zip.NewWriter(&buf) 232 err = copyZipFiles(zipWriter, zr) 233 if err != nil { 234 return buf, err 235 } 236 237 // Encode the license to JSON, remove the trailing newline 238 // write the buffer in the zip 239 licenseBytes, err := json.Marshal(lic) 240 if err != nil { 241 return buf, err 242 } 243 244 licenseBytes = bytes.TrimRight(licenseBytes, "\n") 245 246 location := epub.LicenseFile 247 if isWebPub(zr) { 248 location = "license.lcpl" 249 } 250 251 licenseWriter, err := zipWriter.Create(location) 252 if err != nil { 253 return buf, err 254 } 255 256 _, err = licenseWriter.Write(licenseBytes) 257 if err != nil { 258 return 259 } 260 261 return buf, zipWriter.Close() 262 } 263 264 // GetTestLicense returns an existing license, 265 // selected by a license id and a partial license 266 // set to the last information generated on this server. This is only for test. 267 func GetTestLicense(w http.ResponseWriter, r *http.Request, s Server) { 268 269 // Active only in test mode, if 270 if !config.Config.TestMode { 271 problem.Error(w, r, problem.Problem{Detail: "The server is not in test mode"}, http.StatusBadRequest) 272 return 273 } 274 if lastGeneratedLicense.User.ID == "" { 275 problem.Error(w, r, problem.Problem{Detail: "No previous generated license"}, http.StatusBadRequest) 276 return 277 } 278 279 vars := mux.Vars(r) 280 // get the license id from the request URL 281 licenseID := vars["license_id"] 282 283 // add a log 284 logging.Print("Get a Test License " + licenseID) 285 286 // initialize the license from the info stored in the db. 287 var licOut license.License 288 licOut, e := s.Licenses().Get(licenseID) 289 // process license not found etc. 290 if e == license.ErrNotFound { 291 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusNotFound) 292 return 293 } else if e != nil { 294 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusBadRequest) 295 return 296 } 297 298 // It contains the hashed passphrase, user hint 299 // and other optional user data the provider wants to see embedded in the license 300 licIn := lastGeneratedLicense 301 302 // an input body was sent with the request: 303 // check mandatory information in the partial license 304 err := checkGetLicenseInput(&licIn) 305 if err != nil { 306 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 307 return 308 } 309 // copy useful data from licIn to LicOut 310 copyInputToLicense(&licIn, &licOut) 311 // build the license 312 err = buildLicense(&licOut, s, true) 313 if err != nil { 314 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 315 return 316 } 317 318 // set the http headers 319 w.Header().Add("Content-Type", api.ContentType_LCP_JSON) 320 w.Header().Add("Content-Disposition", `attachment; filename="license.lcpl"`) 321 w.WriteHeader(http.StatusOK) 322 // send back the license 323 // do not escape characters in the json payload 324 enc := json.NewEncoder(w) 325 enc.SetEscapeHTML(false) 326 enc.Encode(licOut) 327 } 328 329 // GetLicense returns an existing license, 330 // selected by a license id and a partial license both given as input. 331 // If the input partial license is absent a partial license is returned 332 func GetLicense(w http.ResponseWriter, r *http.Request, s Server) { 333 334 vars := mux.Vars(r) 335 // get the license id from the request URL 336 licenseID := vars["license_id"] 337 338 // add a log 339 logging.Print("Get the License " + licenseID) 340 341 // initialize the license from the info stored in the db. 342 var licOut license.License 343 licOut, e := s.Licenses().Get(licenseID) 344 // process license not found etc. 345 if e == license.ErrNotFound { 346 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusNotFound) 347 return 348 } else if e != nil { 349 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusBadRequest) 350 return 351 } 352 // get the input body. 353 // It contains the hashed passphrase, user hint 354 // and other optional user data the provider wants to see embedded in the license 355 var err error 356 var licIn license.License 357 err = DecodeJSONLicense(r, &licIn) 358 // error parsing the input body 359 if err != nil { 360 // if there was no partial license given as payload, 361 if err.Error() == "EOF" { 362 // The use case is a frontend that needs to get up to date license rights 363 log.Println("No payload, get a partial license") 364 365 // add useful http headers 366 w.Header().Add("Content-Type", api.ContentType_LCP_JSON) 367 w.WriteHeader(http.StatusPartialContent) 368 // send back the partial license 369 // do not escape characters 370 enc := json.NewEncoder(w) 371 enc.SetEscapeHTML(false) 372 enc.Encode(licOut) 373 return 374 } else { 375 // unknown error 376 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 377 return 378 } 379 } 380 381 // an input body was sent with the request: 382 // check mandatory information in the partial license 383 err = checkGetLicenseInput(&licIn) 384 if err != nil { 385 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 386 return 387 } 388 // copy useful data from licIn to LicOut 389 copyInputToLicense(&licIn, &licOut) 390 // build the license 391 err = buildLicense(&licOut, s, true) 392 if err != nil { 393 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 394 return 395 } 396 397 // set the http headers 398 w.Header().Add("Content-Type", api.ContentType_LCP_JSON) 399 w.Header().Add("Content-Disposition", `attachment; filename="license.lcpl"`) 400 w.WriteHeader(http.StatusOK) 401 // send back the license 402 // do not escape characters in the json payload 403 enc := json.NewEncoder(w) 404 enc.SetEscapeHTML(false) 405 enc.Encode(licOut) 406 } 407 408 // GenerateLicense generates and returns a new license, 409 // for a given content identified by its id 410 // plus a partial license given as input 411 func GenerateLicense(w http.ResponseWriter, r *http.Request, s Server) { 412 413 vars := mux.Vars(r) 414 // get the content id from the request URL 415 contentID := vars["content_id"] 416 417 // get the input body 418 // note: no need to create licIn / licOut here, as the input body contains 419 // info that we want to keep in the full license. 420 var lic license.License 421 err := DecodeJSONLicense(r, &lic) 422 if err != nil { 423 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 424 return 425 } 426 if config.Config.TestMode { 427 // save the partial license for later use on get license 428 lastGeneratedLicense = lic 429 } 430 431 // check mandatory information in the input body 432 err = checkGenerateLicenseInput(&lic) 433 if err != nil { 434 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 435 return 436 } 437 // init the license with an id and issue date 438 license.Initialize(contentID, &lic) 439 440 // add a log 441 logging.Print("Generate the License " + lic.ID + " for Content " + contentID + " and User " + lic.User.ID) 442 443 // normalize the start and end date, UTC, no milliseconds 444 setRights(&lic) 445 446 // build the license 447 err = buildLicense(&lic, s, false) 448 if err != nil { 449 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 450 return 451 } 452 453 // store the license in the db 454 err = s.Licenses().Add(lic) 455 if err != nil { 456 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 457 //problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: contentID}, http.StatusInternalServerError) 458 return 459 } 460 461 // set http headers 462 w.Header().Add("Content-Type", api.ContentType_LCP_JSON) 463 w.Header().Add("Content-Disposition", `attachment; filename="license.lcpl"`) 464 w.WriteHeader(http.StatusCreated) 465 // send back the license 466 // do not escape characters 467 enc := json.NewEncoder(w) 468 enc.SetEscapeHTML(false) 469 enc.Encode(lic) 470 471 // notify the lsd server of the creation of the license. 472 // this is an asynchronous call. 473 go notifyLsdServer(lic, s) 474 } 475 476 // GetProtectedPublication returns a protected publication 477 // for a given license identified by its id 478 // plus a partial license given as input 479 func GetProtectedPublication(w http.ResponseWriter, r *http.Request, s Server) { 480 481 vars := mux.Vars(r) 482 licenseID := vars["license_id"] 483 484 // add a log 485 logging.Print("Get a Licensed publication for License " + licenseID) 486 487 // get the input body 488 var licIn license.License 489 err := DecodeJSONLicense(r, &licIn) 490 if err != nil { 491 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 492 return 493 } 494 // check mandatory information in the input body 495 err = checkGetLicenseInput(&licIn) 496 if err != nil { 497 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 498 return 499 } 500 // initialize the license from the info stored in the db. 501 licOut, e := s.Licenses().Get(licenseID) 502 // process license not found etc. 503 if e == license.ErrNotFound { 504 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusNotFound) 505 return 506 } else if e != nil { 507 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusBadRequest) 508 return 509 } 510 // copy useful data from licIn to LicOut 511 copyInputToLicense(&licIn, &licOut) 512 // build the license 513 err = buildLicense(&licOut, s, true) 514 if err != nil { 515 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 516 return 517 } 518 // build a protected publication 519 buf, err := buildProtectedPublication(&licOut, s) 520 if err == storage.ErrNotFound { 521 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: licOut.ContentID}, http.StatusNotFound) 522 return 523 } else if err != nil { 524 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: licOut.ContentID}, http.StatusInternalServerError) 525 return 526 } 527 // get the content location to fill an http header; this will be the name of the downloaded file. 528 content, err1 := s.Index().Get(licOut.ContentID) 529 if err1 != nil { 530 problem.Error(w, r, problem.Problem{Detail: err1.Error(), Instance: licOut.ContentID}, http.StatusInternalServerError) 531 return 532 } 533 location := content.Location 534 535 // set HTTP headers 536 w.Header().Add("Content-Type", epub.ContentType_EPUB) 537 w.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, location)) 538 // FIXME: check the use of X-Lcp-License by the caller (frontend?) 539 w.Header().Add("X-Lcp-License", licOut.ID) 540 // must come *after* w.Header().Add()/Set(), but before w.Write() 541 w.WriteHeader(http.StatusCreated) 542 // return the full protected publication to the caller 543 io.Copy(w, &buf) 544 } 545 546 // GenerateProtectedPublication generates and returns a protected publication 547 // for a given content identified by its id 548 // plus a partial license given as input 549 func GenerateProtectedPublication(w http.ResponseWriter, r *http.Request, s Server) { 550 551 vars := mux.Vars(r) 552 contentID := vars["content_id"] 553 554 logging.Print("Generate a Licensed publication for Content " + contentID) 555 556 // get the input body 557 var lic license.License 558 err := DecodeJSONLicense(r, &lic) 559 if err != nil { 560 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 561 return 562 } 563 // check mandatory information in the input body 564 err = checkGenerateLicenseInput(&lic) 565 if err != nil { 566 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 567 return 568 } 569 // init the license with an id and issue date 570 license.Initialize(contentID, &lic) 571 // normalize the start and end date, UTC, no milliseconds 572 setRights(&lic) 573 574 // build the license 575 err = buildLicense(&lic, s, false) 576 if err != nil { 577 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 578 return 579 } 580 // store the license in the db 581 err = s.Licenses().Add(lic) 582 if err != nil { 583 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: contentID}, http.StatusInternalServerError) 584 return 585 } 586 587 // notify the lsd server of the creation of the license 588 go notifyLsdServer(lic, s) 589 590 // build a licenced publication 591 buf, err := buildProtectedPublication(&lic, s) 592 if err == storage.ErrNotFound { 593 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: lic.ContentID}, http.StatusNotFound) 594 return 595 } else if err != nil { 596 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: lic.ContentID}, http.StatusInternalServerError) 597 return 598 } 599 600 // get the content location to fill an http header 601 content, err1 := s.Index().Get(lic.ContentID) 602 if err1 != nil { 603 problem.Error(w, r, problem.Problem{Detail: err1.Error(), Instance: lic.ContentID}, http.StatusInternalServerError) 604 return 605 } 606 location := content.Location 607 608 // set HTTP headers 609 w.Header().Add("Content-Type", epub.ContentType_EPUB) 610 w.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, location)) 611 // FIXME: check the use of X-Lcp-License by the caller (frontend?) 612 w.Header().Add("X-Lcp-License", lic.ID) 613 // must come *after* w.Header().Add()/Set(), but before w.Write() 614 w.WriteHeader(http.StatusCreated) 615 // return the full protected publication to the caller 616 io.Copy(w, &buf) 617 } 618 619 // UpdateLicense updates an existing license. 620 // parameters: 621 // 622 // {license_id} in the calling URL 623 // partial license containing properties which should be updated (and only these) 624 // 625 // return: an http status code (200, 400 or 404) 626 // Usually called from the License Status Server after a renew, return or cancel/revoke action 627 // -> updates the end date. 628 func UpdateLicense(w http.ResponseWriter, r *http.Request, s Server) { 629 630 vars := mux.Vars(r) 631 // get the license id from the request URL 632 licenseID := vars["license_id"] 633 634 // add a log 635 logging.Print("Update the License " + licenseID) 636 637 var licIn license.License 638 err := DecodeJSONLicense(r, &licIn) 639 if err != nil { // no or incorrect (json) partial license found in the body 640 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 641 return 642 } 643 // initialize the license from the info stored in the db. 644 var licOut license.License 645 licOut, e := s.Licenses().Get(licenseID) 646 // process license not found etc. 647 if e == license.ErrNotFound { 648 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusNotFound) 649 return 650 } else if e != nil { 651 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusBadRequest) 652 return 653 } 654 // update licOut using information found in licIn 655 if licIn.User.ID != "" { 656 log.Println("new user id: ", licIn.User.ID) 657 licOut.User.ID = licIn.User.ID 658 } 659 if licIn.Provider != "" { 660 log.Println("new provider: ", licIn.Provider) 661 licOut.Provider = licIn.Provider 662 } 663 if licIn.ContentID != "" { 664 log.Println("new content id: ", licIn.ContentID) 665 licOut.ContentID = licIn.ContentID 666 } 667 if licIn.Rights.Print != nil { 668 log.Println("new right, print: ", *licIn.Rights.Print) 669 licOut.Rights.Print = licIn.Rights.Print 670 } 671 if licIn.Rights.Copy != nil { 672 log.Println("new right, copy: ", *licIn.Rights.Copy) 673 licOut.Rights.Copy = licIn.Rights.Copy 674 } 675 if licIn.Rights.Start != nil { 676 log.Println("new right, start: ", *licIn.Rights.Start) 677 licOut.Rights.Start = licIn.Rights.Start 678 } 679 if licIn.Rights.End != nil { 680 log.Println("new right, end: ", *licIn.Rights.End) 681 licOut.Rights.End = licIn.Rights.End 682 } 683 // update the license in the database 684 err = s.Licenses().Update(licOut) 685 if err != nil { 686 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 687 return 688 } 689 } 690 691 // ListLicenses returns a JSON struct with information about the existing licenses 692 // parameters: 693 // 694 // page: page number 695 // per_page: number of items par page 696 func ListLicenses(w http.ResponseWriter, r *http.Request, s Server) { 697 698 var page int64 699 var perPage int64 700 var err error 701 if r.FormValue("page") != "" { 702 page, err = strconv.ParseInt((r).FormValue("page"), 10, 32) 703 if err != nil { 704 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 705 return 706 } 707 } else { 708 page = 1 709 } 710 if r.FormValue("per_page") != "" { 711 perPage, err = strconv.ParseInt((r).FormValue("per_page"), 10, 32) 712 if err != nil { 713 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 714 return 715 } 716 } else { 717 perPage = 30 718 } 719 if page > 0 { //pagenum starting at 0 in code, but user interface starting at 1 720 page-- 721 } 722 if page < 0 { 723 problem.Error(w, r, problem.Problem{Detail: "page must be positive integer"}, http.StatusBadRequest) 724 return 725 } 726 licenses := make([]license.LicenseReport, 0) 727 728 // add a log 729 logging.Print("List Licenses (page " + strconv.Itoa(int(page)) + ", count " + strconv.Itoa(int(perPage)) + ")") 730 731 fn := s.Licenses().ListAll(int(perPage), int(page)) 732 for it, err := fn(); err == nil; it, err = fn() { 733 licenses = append(licenses, it) 734 } 735 if len(licenses) > 0 { 736 nextPage := strconv.Itoa(int(page) + 1) 737 w.Header().Set("Link", "</licenses/?page="+nextPage+">; rel=\"next\"; title=\"next\"") 738 } 739 if page > 1 { 740 previousPage := strconv.Itoa(int(page) - 1) 741 w.Header().Set("Link", "</licenses/?page="+previousPage+">; rel=\"previous\"; title=\"previous\"") 742 } 743 w.Header().Set("Content-Type", api.ContentType_JSON) 744 745 enc := json.NewEncoder(w) 746 // do not escape characters 747 enc.SetEscapeHTML(false) 748 err = enc.Encode(licenses) 749 if err != nil { 750 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 751 return 752 } 753 } 754 755 // ListLicensesForContent lists all licenses associated with a given content 756 // parameters: 757 // 758 // content_id: content identifier 759 // page: page number (default 1) 760 // per_page: number of items par page (default 30) 761 func ListLicensesForContent(w http.ResponseWriter, r *http.Request, s Server) { 762 763 vars := mux.Vars(r) 764 var page int64 765 var perPage int64 766 var err error 767 contentID := vars["content_id"] 768 769 //check if the license exists 770 _, err = s.Index().Get(contentID) 771 if err == index.ErrNotFound { 772 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 773 return 774 } //other errors pass, but will probably reoccur 775 if r.FormValue("page") != "" { 776 page, err = strconv.ParseInt(r.FormValue("page"), 10, 32) 777 if err != nil { 778 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 779 return 780 } 781 } else { 782 page = 1 783 } 784 785 if r.FormValue("per_page") != "" { 786 perPage, err = strconv.ParseInt((r).FormValue("per_page"), 10, 32) 787 if err != nil { 788 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 789 return 790 } 791 } else { 792 perPage = 30 793 } 794 if page > 0 { 795 page-- //pagenum starting at 0 in code, but user interface starting at 1 796 } 797 if page < 0 { 798 problem.Error(w, r, problem.Problem{Detail: "page must be positive integer"}, http.StatusBadRequest) 799 return 800 } 801 licenses := make([]license.LicenseReport, 0) 802 803 // add a log 804 logging.Print("List Licenses for publication " + contentID + " (page " + strconv.Itoa(int(page)) + ", count " + strconv.Itoa(int(perPage)) + ")") 805 806 fn := s.Licenses().ListByContentID(contentID, int(perPage), int(page)) 807 for it, err := fn(); err == nil; it, err = fn() { 808 licenses = append(licenses, it) 809 } 810 if len(licenses) > 0 { 811 nextPage := strconv.Itoa(int(page) + 1) 812 w.Header().Set("Link", "</licenses/?page="+nextPage+">; rel=\"next\"; title=\"next\"") 813 } 814 if page > 1 { 815 previousPage := strconv.Itoa(int(page) - 1) 816 w.Header().Set("Link", "</licenses/?page="+previousPage+">; rel=\"previous\"; title=\"previous\"") 817 } 818 w.Header().Set("Content-Type", api.ContentType_JSON) 819 enc := json.NewEncoder(w) 820 // do not escape characters 821 enc.SetEscapeHTML(false) 822 err = enc.Encode(licenses) 823 if err != nil { 824 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 825 return 826 } 827 828 } 829 830 // DecodeJSONLicense decodes a license formatted in json and returns a license object 831 func DecodeJSONLicense(r *http.Request, lic *license.License) error { 832 833 var dec *json.Decoder 834 835 if ctype := r.Header["Content-Type"]; len(ctype) > 0 && ctype[0] == api.ContentType_FORM_URL_ENCODED { 836 buf := bytes.NewBufferString(r.PostFormValue("data")) 837 dec = json.NewDecoder(buf) 838 } else { 839 dec = json.NewDecoder(r.Body) 840 } 841 842 err := dec.Decode(&lic) 843 844 if err != nil && err.Error() != "EOF" { 845 log.Print("Decode license: invalid json structure") 846 } 847 return err 848 } 849 850 // notifyLsdServer informs the License Status Server of the creation of a new license 851 // and saves the result of the http request in the DB (using *Store) 852 func notifyLsdServer(l license.License, s Server) { 853 854 if config.Config.LsdServer.PublicBaseUrl != "" { 855 var lsdClient = &http.Client{ 856 Timeout: time.Second * 10, 857 } 858 pr, pw := io.Pipe() 859 defer pr.Close() 860 go func() { 861 _ = json.NewEncoder(pw).Encode(l) 862 pw.Close() // signal end writing 863 }() 864 req, err := http.NewRequest("PUT", config.Config.LsdServer.PublicBaseUrl+"/licenses", pr) 865 if err != nil { 866 return 867 } 868 // set credentials on lsd request 869 notifyAuth := config.Config.LsdNotifyAuth 870 if notifyAuth.Username != "" { 871 req.SetBasicAuth(notifyAuth.Username, notifyAuth.Password) 872 } 873 874 req.Header.Add("Content-Type", api.ContentType_LCP_JSON) 875 876 response, err := lsdClient.Do(req) 877 if err != nil { 878 log.Println("Error Notify LsdServer of new License (" + l.ID + "):" + err.Error()) 879 _ = s.Licenses().UpdateLsdStatus(l.ID, -1) 880 } else { 881 defer req.Body.Close() 882 _ = s.Licenses().UpdateLsdStatus(l.ID, int32(response.StatusCode)) 883 } 884 } 885 }