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 = &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, 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 = &currentTime
   707  	licenseStatus.Updated.Status = &currentTime
   708  	licenseStatus.Updated.License = &currentTime
   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 = &currentTime
   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  }