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 = &currentTime
    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 = &currentTime
   712  	licenseStatus.Updated.Status = &currentTime
   713  	licenseStatus.Updated.License = &currentTime
   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 = &currentTime
   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  }