├── .gitignore ├── CONTRIBUTIONS.md ├── LICENSE.md ├── README.md ├── api └── common_server.go ├── authentication ├── md5password │ └── makeMd5Password.go └── passwords.htpasswd ├── config └── config.go ├── crypto ├── aes_cbc.go ├── aes_cbc_test.go ├── aes_gcm.go ├── aes_gcm_test.go ├── encrypt.go ├── key.go ├── key_test.go ├── pad.go └── pad_test.go ├── dbmodel ├── mysql_db_setup_frontend.sql ├── mysql_db_setup_lcpserver.sql ├── mysql_db_setup_lsdserver.sql ├── postgres _db_setup_frontend.sql ├── postgres_db_setup_lcpserver.sql ├── postgres_db_setup_lsdserver.sql ├── sqlite _db_setup_frontend.sql ├── sqlite_db_setup_lcpserver.sql ├── sqlite_db_setup_lsdserver.sql ├── sqlserver _db_setup_frontend.sql ├── sqlserver_db_setup_lcpserver.sql └── sqlserver_db_setup_lsdserver.sql ├── dbutils ├── dbutils.go └── dbutils_test.go ├── encrypt ├── notify_server.go ├── process_encrypt.go └── store_publication.go ├── epub ├── epub.go ├── opf │ └── opf.go ├── reader.go ├── reader_test.go ├── utils.go ├── writer.go └── writer_test.go ├── frontend ├── api │ ├── common.go │ ├── dashboard.go │ ├── license.go │ ├── publication.go │ ├── purchase.go │ ├── repository.go │ └── user.go ├── frontend.go ├── manage │ ├── .editorconfig │ ├── .travis.yml │ ├── LICENSE-angular-quickstart │ ├── README.md │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── components │ │ │ ├── app.component.html │ │ │ ├── app.component.ts │ │ │ ├── lsd-structs.ts │ │ │ ├── lsd.service.ts │ │ │ ├── partialLicense.ts │ │ │ ├── purchase-list-component.ts │ │ │ ├── purchase.service.ts │ │ │ ├── purchase.ts │ │ │ ├── purchases.css │ │ │ ├── purchases.html │ │ │ ├── resource-list-component.ts │ │ │ ├── resource-list.html │ │ │ ├── resource.css │ │ │ ├── resource.service.ts │ │ │ ├── resource.ts │ │ │ ├── rightsFromPartialLicense.pipe.ts │ │ │ ├── user-component.ts │ │ │ ├── user-list-component.ts │ │ │ ├── user-list.html │ │ │ ├── user.css │ │ │ ├── user.html │ │ │ ├── user.service.ts │ │ │ └── user.ts │ │ ├── crud │ │ │ ├── crud-item.ts │ │ │ └── crud.service.ts │ │ ├── dashboard │ │ │ ├── dashboard-info.component.html │ │ │ ├── dashboard-info.component.ts │ │ │ ├── dashboard-routing.module.ts │ │ │ ├── dashboard.component.html │ │ │ ├── dashboard.component.ts │ │ │ ├── dashboard.module.ts │ │ │ ├── dashboard.service.ts │ │ │ └── dashboardInfo.ts │ │ ├── license │ │ │ ├── license-info.component.html │ │ │ ├── license-info.component.ts │ │ │ ├── license-routing.module.ts │ │ │ ├── license.component.html │ │ │ ├── license.component.ts │ │ │ ├── license.module.ts │ │ │ ├── license.service.ts │ │ │ └── license.ts │ │ ├── lsd │ │ │ ├── license-status.ts │ │ │ ├── lsd.module.ts │ │ │ └── lsd.service.ts │ │ ├── main.ts │ │ ├── not-found.component.ts │ │ ├── publication │ │ │ ├── master-file.ts │ │ │ ├── publication-add.component.html │ │ │ ├── publication-add.component.ts │ │ │ ├── publication-edit.component.html │ │ │ ├── publication-edit.component.ts │ │ │ ├── publication-form.component.html │ │ │ ├── publication-form.component.ts │ │ │ ├── publication-list.component.html │ │ │ ├── publication-list.component.ts │ │ │ ├── publication-routing.module.ts │ │ │ ├── publication.module.ts │ │ │ ├── publication.service.ts │ │ │ └── publication.ts │ │ ├── purchase │ │ │ ├── purchase-add.component.html │ │ │ ├── purchase-add.component.ts │ │ │ ├── purchase-edit.component.html │ │ │ ├── purchase-edit.component.ts │ │ │ ├── purchase-form.component.html │ │ │ ├── purchase-form.component.ts │ │ │ ├── purchase-list.component.html │ │ │ ├── purchase-list.component.ts │ │ │ ├── purchase-routing.module.ts │ │ │ ├── purchase-status.component.html │ │ │ ├── purchase-status.component.ts │ │ │ ├── purchase.module.ts │ │ │ ├── purchase.service.ts │ │ │ └── purchase.ts │ │ ├── shared │ │ │ ├── header │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.ts │ │ │ │ └── header.module.ts │ │ │ ├── pipes │ │ │ │ ├── sort.module.ts │ │ │ │ └── sort.pipe.ts │ │ │ └── sidebar │ │ │ │ ├── sidebar.component.html │ │ │ │ ├── sidebar.component.ts │ │ │ │ ├── sidebar.module.ts │ │ │ │ └── sidebar.service.ts │ │ └── user │ │ │ ├── user-add.component.html │ │ │ ├── user-add.component.ts │ │ │ ├── user-edit.component.html │ │ │ ├── user-edit.component.ts │ │ │ ├── user-form.component.html │ │ │ ├── user-form.component.ts │ │ │ ├── user-list.component.html │ │ │ ├── user-list.component.ts │ │ │ ├── user-routing.module.ts │ │ │ ├── user.module.ts │ │ │ ├── user.service.ts │ │ │ └── user.ts │ ├── assets │ │ └── sass │ │ │ ├── app.scss │ │ │ └── main.scss │ ├── bs-config.js │ ├── e2e │ │ ├── app.e2e-spec.js │ │ └── app.e2e-spec.ts │ ├── favicon.ico │ ├── index.html │ ├── karma-test-shim.js │ ├── karma.conf.js │ ├── package.json │ ├── protractor.config.js │ ├── styles.css │ ├── systemjs.config.extras.js │ ├── systemjs.config.js │ ├── tsconfig.json │ ├── tslint.json │ └── typings │ │ └── file-saver │ │ └── index.d.ts ├── package-lock.json ├── server │ ├── server.go │ └── server_test.go ├── webdashboard │ └── webdashboard.go ├── weblicense │ └── weblicense.go ├── webpublication │ └── webpublication.go ├── webpurchase │ └── webpurchase.go ├── webrepository │ └── webrepository.go └── webuser │ └── webuser.go ├── go.mod ├── go.sum ├── index ├── index.go └── index_test.go ├── lcpencrypt ├── get_content_info.go └── lcpencrypt.go ├── lcpserver ├── api │ ├── license.go │ └── store.go ├── lcpserver.go └── server │ ├── server.go │ └── server_test.go ├── license ├── license.go ├── license_test.go ├── store.go ├── store_test.go └── user_key.go ├── license_statuses ├── license_status.go ├── license_statuses.go └── license_statuses_test.go ├── logging └── logging.go ├── lsdserver ├── api │ ├── freshlicense.go │ ├── freshlicense_test.go │ └── license_status.go ├── lsdserver.go └── server │ ├── server.go │ └── server_test.go ├── pack ├── pack.go ├── pack_test.go ├── pipeline.go ├── rwppackage.go ├── rwppackage_test.go ├── samples │ ├── basic.webpub │ └── w3cman1.json ├── w3cpackage.go └── w3cpackage_test.go ├── problem └── problem.go ├── rwpm ├── metadata.go ├── metadata_test.go ├── publication.go ├── w3cpublication.go └── w3cpublication_test.go ├── sign ├── canon.go ├── canon_test.go ├── cert │ ├── sample_ecdsa.crt │ ├── sample_ecdsa.pem │ ├── sample_rsa.crt │ └── sample_rsa.pem ├── sign.go └── sign_test.go ├── status └── status.go ├── storage ├── fs.go ├── fs_test.go ├── interface.go ├── nostore.go └── s3.go ├── test ├── cert │ ├── cert-edrlab-test.pem │ └── privkey-edrlab-test.pem ├── config.yaml └── samples │ ├── lorem.epub │ ├── sample-with-space.epub │ └── sample.epub ├── transactions ├── transactions.go └── transactions_test.go └── xmlenc └── encryption.go /.gitignore: -------------------------------------------------------------------------------- 1 | lcpserve 2 | readium-lcp-server 3 | lcpencrypt/lcpencrypt 4 | files/ 5 | *.sqlite* 6 | lcpserver/manage/config.js 7 | .vscode/launch.json 8 | debug 9 | *.a 10 | *.lib 11 | *.exe 12 | *.yaml 13 | !test/config.yaml 14 | **/manage/config.js 15 | frontend/manage/node_modules/* 16 | frontend/manage/dist/* 17 | frontend/manage/js/* 18 | frontend/manage/js/components/* 19 | *.map 20 | *.htpasswd 21 | lcpserver/test.sqllite 22 | .DS_Store 23 | .vscode 24 | lcpencrypt/.DS_Store 25 | lcpserver/.DS_Store 26 | npm-debug.log 27 | manage/config.js 28 | -------------------------------------------------------------------------------- /CONTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | # Readium LCP Server contributors 2 | 3 | The document lists the significant contributors of the Readium LCP Server project. 4 | 5 | This does not necessarily list everyone who has contributed code, 6 | but represents people and organizations having made a significant 7 | investment in time on this project. 8 | 9 | They are listed in order of participation to the project. 10 | 11 | To see the full list of contributors, please check the Github revision history. 12 | 13 | - Jean-Philippe Bougie (De Marque), who initiated the project in 2014 14 | - Rémi Bauzac (TEA-ebooks) 15 | - Hanna Boichenko (EDRLab) 16 | - Léo Stéfani (noop for EDRLab) 17 | - Cyrille Lebeaupin (noop for EDRLab) 18 | - Stephan Nemegeer (ePagine) 19 | - Daniel Weck (EDRLab) 20 | - Laurent Le Meur (EDRLab) 21 | 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Readium 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /authentication/md5password/makeMd5Password.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package main 27 | 28 | import ( 29 | "log" 30 | "math/rand" 31 | "os" 32 | "strconv" 33 | "time" 34 | 35 | "github.com/abbot/go-http-auth" 36 | ) 37 | 38 | func main() { 39 | 40 | var salt []byte 41 | var magic []byte 42 | if len(os.Args) < 2 { 43 | panic("need a password") 44 | } 45 | if len(os.Args) > 2 { 46 | salt = []byte(os.Args[2]) 47 | } else { 48 | r := rand.New(rand.NewSource(int64(time.Now().Unix()))) 49 | salt = []byte(strconv.Itoa(r.Int())) 50 | } 51 | if len(os.Args) > 3 { 52 | magic = []byte("$" + string(os.Args[3]) + "$") 53 | } else { 54 | magic = []byte("$" + "$") 55 | } 56 | 57 | log.Println(string(auth.MD5Crypt([]byte(os.Args[1]), salt, magic))) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /authentication/passwords.htpasswd: -------------------------------------------------------------------------------- 1 | Hanna:$apr1$OMWGq53X$Qf17b.ezwEM947Vrr/oTh0 2 | User:$apr1$lldfYQA5$8fVeTVyKsiPeqcBWrjBKM. 3 | Stefaan:$$8812069051407036158$00w3pIjfRqawvNxw9gzZs1 -------------------------------------------------------------------------------- /crypto/aes_gcm.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package crypto 27 | 28 | import ( 29 | "crypto/aes" 30 | "crypto/cipher" 31 | "encoding/binary" 32 | "io" 33 | "io/ioutil" 34 | ) 35 | 36 | type gcmEncrypter struct { 37 | counter uint64 38 | } 39 | 40 | func (e gcmEncrypter) Signature() string { 41 | return "http://www.w3.org/2009/xmlenc11#aes256-gcm" 42 | } 43 | 44 | func (e gcmEncrypter) GenerateKey() (ContentKey, error) { 45 | slice, err := GenerateKey(aes256keyLength) 46 | return ContentKey(slice), err 47 | } 48 | 49 | func (e *gcmEncrypter) Encrypt(key ContentKey, r io.Reader, w io.Writer) error { 50 | block, err := aes.NewCipher(key) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | counter := e.counter 56 | e.counter++ 57 | 58 | gcm, err := cipher.NewGCM(block) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | nonce := make([]byte, gcm.NonceSize()) 64 | binary.BigEndian.PutUint64(nonce, counter) 65 | 66 | data, err := ioutil.ReadAll(r) 67 | out := gcm.Seal(nonce, nonce, data, nil) 68 | 69 | _, err = w.Write(out) 70 | 71 | return err 72 | } 73 | 74 | func NewAESGCMEncrypter() Encrypter { 75 | return &gcmEncrypter{} 76 | } -------------------------------------------------------------------------------- /crypto/aes_gcm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package crypto 27 | 28 | import ( 29 | "bytes" 30 | "crypto/aes" 31 | "crypto/cipher" 32 | "encoding/hex" 33 | "testing" 34 | ) 35 | 36 | func TestEncryptGCM(t *testing.T) { 37 | key, _ := hex.DecodeString("11754cd72aec309bf52f7687212e8957") 38 | 39 | encrypter := NewAESGCMEncrypter() 40 | 41 | data := []byte("The quick brown fox jumps over the lazy dog") 42 | 43 | r := bytes.NewReader(data) 44 | w := new(bytes.Buffer) 45 | 46 | if err := encrypter.Encrypt(ContentKey(key), r, w); err != nil { 47 | t.Fatal("Encryption failed", err) 48 | } 49 | 50 | block, _ := aes.NewCipher(key) 51 | gcm, _ := cipher.NewGCM(block) 52 | 53 | out := w.Bytes() 54 | t.Logf("nonce size: %#v", gcm.NonceSize()) 55 | t.Logf("nonce: %#v", out[0:gcm.NonceSize()]) 56 | t.Logf("ciphertext: %#v", out[gcm.NonceSize():]) 57 | clear := make([]byte, 0) 58 | clear, err := gcm.Open(clear, out[0:gcm.NonceSize()], out[gcm.NonceSize():], nil) 59 | 60 | if err != nil { 61 | t.Fatal("Decryption failed", err) 62 | } 63 | 64 | if diff := bytes.Compare(data, clear); diff != 0 { 65 | t.Logf("Original: %#v", data) 66 | t.Logf("After cycle: %#v", clear) 67 | t.Errorf("Expected encryption-decryption to return original") 68 | } 69 | } -------------------------------------------------------------------------------- /crypto/key.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package crypto 27 | 28 | import ( 29 | "crypto/rand" 30 | ) 31 | 32 | type ContentKey []byte 33 | 34 | func GenerateKey(size int) ([]byte, error) { 35 | k := make([]byte, size) 36 | 37 | _, err := rand.Read(k) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return k, nil 43 | } 44 | -------------------------------------------------------------------------------- /crypto/key_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package crypto 27 | 28 | import "testing" 29 | 30 | func TestGenerateKey(t *testing.T) { 31 | buf, err := GenerateKey(aes256keyLength) 32 | 33 | if err != nil { 34 | t.Error(err) 35 | } 36 | 37 | if len(buf) != 32 { 38 | t.Error("it should be a 32-byte long buffer") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /dbmodel/mysql_db_setup_frontend.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `publication` ( 2 | `id` int(11) PRIMARY KEY AUTO_INCREMENT, 3 | `uuid` varchar(255) NOT NULL, /* == content id */ 4 | `title` varchar(255) NOT NULL, 5 | `status` varchar(255) NOT NULL 6 | ); 7 | 8 | CREATE INDEX uuid_index ON publication (`uuid`); 9 | 10 | CREATE TABLE `user` ( 11 | `id` int(11) PRIMARY KEY AUTO_INCREMENT, 12 | `uuid` varchar(255) NOT NULL, 13 | `name` varchar(64) NOT NULL, 14 | `email` varchar(64) NOT NULL, 15 | `password` varchar(64) NOT NULL, 16 | `hint` varchar(64) NOT NULL 17 | ); 18 | 19 | CREATE TABLE `purchase` ( 20 | `id` int(11) PRIMARY KEY AUTO_INCREMENT, 21 | `uuid` varchar(255) NOT NULL, 22 | `publication_id` int(11) NOT NULL, 23 | `user_id` int(11) NOT NULL, 24 | `license_uuid` varchar(255) NULL, 25 | `type` varchar(32) NOT NULL, 26 | `transaction_date` datetime, 27 | `start_date` datetime, 28 | `end_date` datetime, 29 | `status` varchar(255) NOT NULL, 30 | FOREIGN KEY (`publication_id`) REFERENCES `publication` (`id`), 31 | FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) 32 | ); 33 | 34 | CREATE INDEX `idx_purchase` ON `purchase` (`license_uuid`); 35 | 36 | CREATE TABLE `license_view` ( 37 | `id` int(11) PRIMARY KEY AUTO_INCREMENT, 38 | `uuid` varchar(255) NOT NULL, 39 | `device_count` int(11) NOT NULL, 40 | `status` varchar(255) NOT NULL, 41 | `message` varchar(255) NOT NULL 42 | ); -------------------------------------------------------------------------------- /dbmodel/mysql_db_setup_lcpserver.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `content` ( 2 | `id` varchar(255) PRIMARY KEY NOT NULL, 3 | `encryption_key` varbinary(64) NOT NULL, 4 | `location` text NOT NULL, 5 | `length` bigint(20), 6 | `sha256` varchar(64), 7 | `type` varchar(255) NOT NULL DEFAULT 'application/epub+zip' 8 | ); 9 | 10 | CREATE TABLE `license` ( 11 | `id` varchar(255) PRIMARY KEY NOT NULL, 12 | `user_id` varchar(255) NOT NULL, 13 | `provider` varchar(255) NOT NULL, 14 | `issued` datetime NOT NULL, 15 | `updated` datetime DEFAULT NULL, 16 | `rights_print` int(11) DEFAULT NULL, 17 | `rights_copy` int(11) DEFAULT NULL, 18 | `rights_start` datetime DEFAULT NULL, 19 | `rights_end` datetime DEFAULT NULL, 20 | `content_fk` varchar(255) NOT NULL, 21 | `lsd_status` int(11) default 0, 22 | FOREIGN KEY(content_fk) REFERENCES content(id) 23 | ); -------------------------------------------------------------------------------- /dbmodel/mysql_db_setup_lsdserver.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `license_status` ( 2 | `id` int(11) PRIMARY KEY AUTO_INCREMENT, 3 | `status` int(11) NOT NULL, 4 | `license_updated` datetime NOT NULL, 5 | `status_updated` datetime NOT NULL, 6 | `device_count` int(11) DEFAULT NULL, 7 | `potential_rights_end` datetime DEFAULT NULL, 8 | `license_ref` varchar(255) NOT NULL, 9 | `rights_end` datetime DEFAULT NULL 10 | ); 11 | 12 | CREATE INDEX `license_ref_index` ON `license_status` (`license_ref`); 13 | 14 | CREATE TABLE `event` ( 15 | `id` int(11) PRIMARY KEY AUTO_INCREMENT, 16 | `device_name` varchar(255) DEFAULT NULL, 17 | `timestamp` datetime NOT NULL, 18 | `type` int NOT NULL, 19 | `device_id` varchar(255) DEFAULT NULL, 20 | `license_status_fk` int NOT NULL, 21 | FOREIGN KEY(`license_status_fk`) REFERENCES `license_status` (`id`) 22 | ); 23 | 24 | CREATE INDEX `license_status_fk_index` on `event` (`license_status_fk`); -------------------------------------------------------------------------------- /dbmodel/postgres _db_setup_frontend.sql: -------------------------------------------------------------------------------- 1 | CREATE SEQUENCE publication_seq; 2 | 3 | CREATE TABLE publication ( 4 | id int PRIMARY KEY DEFAULT NEXTVAL ('publication_seq'), 5 | uuid varchar(255) NOT NULL, /* == content id */ 6 | title varchar(255) NOT NULL, 7 | status varchar(255) NOT NULL 8 | ); 9 | 10 | CREATE INDEX uuid_index ON publication (uuid); 11 | 12 | CREATE SEQUENCE user_seq; 13 | 14 | CREATE TABLE "user" ( 15 | id int PRIMARY KEY DEFAULT NEXTVAL ('user_seq'), 16 | uuid varchar(255) NOT NULL, 17 | name varchar(64) NOT NULL, 18 | email varchar(64) NOT NULL, 19 | password varchar(64) NOT NULL, 20 | hint varchar(64) NOT NULL 21 | ); 22 | 23 | CREATE SEQUENCE purchase_seq; 24 | 25 | CREATE TABLE purchase ( 26 | id int PRIMARY KEY DEFAULT NEXTVAL ('purchase_seq'), 27 | uuid varchar(255) NOT NULL, 28 | publication_id int NOT NULL, 29 | user_id int NOT NULL, 30 | license_uuid varchar(255) NULL, 31 | type varchar(32) NOT NULL, 32 | transaction_date timestamp(0), 33 | start_date timestamp(0), 34 | end_date timestamp(0), 35 | status varchar(255) NOT NULL, 36 | FOREIGN KEY (publication_id) REFERENCES publication (id), 37 | FOREIGN KEY (user_id) REFERENCES "user" (id) 38 | ); 39 | 40 | CREATE INDEX idx_purchase ON purchase (license_uuid); 41 | 42 | CREATE SEQUENCE license_view_seq; 43 | 44 | CREATE TABLE license_view ( 45 | id int PRIMARY KEY DEFAULT NEXTVAL ('license_view_seq'), 46 | uuid varchar(255) NOT NULL, 47 | device_count int NOT NULL, 48 | status varchar(255) NOT NULL, 49 | message varchar(255) NOT NULL 50 | ); -------------------------------------------------------------------------------- /dbmodel/postgres_db_setup_lcpserver.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE TABLE content ( 3 | id varchar(255) PRIMARY KEY NOT NULL, 4 | encryption_key bytea NOT NULL, 5 | location text NOT NULL, 6 | length bigint, 7 | sha256 varchar(64), 8 | type varchar(255) NOT NULL DEFAULT 'application/epub+zip' 9 | ); 10 | 11 | -- SQLINES LICENSE FOR EVALUATION USE ONLY 12 | CREATE TABLE license ( 13 | id varchar(255) PRIMARY KEY NOT NULL, 14 | user_id varchar(255) NOT NULL, 15 | provider varchar(255) NOT NULL, 16 | issued timestamp(0) NOT NULL, 17 | updated timestamp(0) DEFAULT NULL, 18 | rights_print int DEFAULT NULL, 19 | rights_copy int DEFAULT NULL, 20 | rights_start timestamp(0) DEFAULT NULL, 21 | rights_end timestamp(0) DEFAULT NULL, 22 | content_fk varchar(255) NOT NULL, 23 | lsd_status int default 0, 24 | FOREIGN KEY(content_fk) REFERENCES content(id) 25 | ); -------------------------------------------------------------------------------- /dbmodel/postgres_db_setup_lsdserver.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE license_status ( 2 | id serial4 NOT NULL, 3 | status smallint NOT NULL, 4 | license_updated timestamp(3) NOT NULL, 5 | status_updated timestamp(3) NOT NULL, 6 | device_count smallint DEFAULT NULL, 7 | potential_rights_end timestamp(3) DEFAULT NULL, 8 | license_ref varchar(255) NOT NULL, 9 | rights_end timestamp(3) DEFAULT NULL, 10 | CONSTRAINT license_status_pkey PRIMARY KEY (id) 11 | ); 12 | 13 | CREATE INDEX license_ref_index ON license_status (license_ref); 14 | 15 | CREATE TABLE event ( 16 | id serial4 NOT NULL, 17 | device_name varchar(255) DEFAULT NULL, 18 | timestamp timestamp(3) NOT NULL, 19 | type int NOT NULL, 20 | device_id varchar(255) DEFAULT NULL, 21 | license_status_fk int NOT NULL, 22 | CONSTRAINT event_pkey PRIMARY KEY (id), 23 | FOREIGN KEY(license_status_fk) REFERENCES license_status(id) 24 | ); 25 | 26 | CREATE INDEX license_status_fk_index on event (license_status_fk); -------------------------------------------------------------------------------- /dbmodel/sqlite _db_setup_frontend.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE publication ( 2 |   id integer NOT NULL PRIMARY KEY, 3 |   uuid varchar(255) NOT NULL, 4 |   title varchar(255) NOT NULL, 5 |   status varchar(255) NOT NULL 6 | ); 7 | 8 | CREATE INDEX uuid_index ON publication (uuid); 9 | 10 | CREATE TABLE purchase ( 11 |   id integer NOT NULL PRIMARY KEY, 12 |   uuid varchar(255) NOT NULL, 13 |   publication_id integer NOT NULL, 14 |   user_id integer NOT NULL, 15 |   license_uuid varchar(255) NULL, 16 |   "type" varchar(32) NOT NULL, 17 | transaction_date datetime, 18 |   start_date datetime, 19 |   end_date datetime, 20 |   status varchar(255) NOT NULL, 21 |   FOREIGN KEY (publication_id) REFERENCES publication(id), 22 |   FOREIGN KEY (user_id) REFERENCES "user"(id) 23 | ); 24 |    25 | CREATE INDEX idx_purchase ON purchase (license_uuid); 26 | 27 | CREATE TABLE "user" ( 28 |   id integer NOT NULL PRIMARY KEY, 29 |   uuid varchar(255) NOT NULL, 30 |   name varchar(64) NOT NULL, 31 |   email varchar(64) NOT NULL, 32 |   password varchar(64) NOT NULL, 33 |   hint varchar(64) NOT NULL 34 | ); 35 | 36 | CREATE TABLE license_view ( 37 |   id integer NOT NULL PRIMARY KEY, 38 |   uuid varchar(255) NOT NULL, 39 |   device_count integer NOT NULL, 40 |   status varchar(255) NOT NULL, 41 |   message varchar(255) NOT NULL 42 | ); 43 | -------------------------------------------------------------------------------- /dbmodel/sqlite_db_setup_lcpserver.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE content ( 2 | id varchar(255) PRIMARY KEY NOT NULL, 3 | encryption_key varchar(64) NOT NULL, 4 | location text NOT NULL, 5 | length bigint, 6 | sha256 varchar(64), 7 | "type" varchar(255) NOT NULL DEFAULT 'application/epub+zip' 8 | ); 9 | 10 | CREATE TABLE license ( 11 | id varchar(255) PRIMARY KEY NOT NULL, 12 | user_id varchar(255) NOT NULL, 13 | provider varchar(255) NOT NULL, 14 | issued datetime NOT NULL, 15 | updated datetime DEFAULT NULL, 16 | rights_print int(11) DEFAULT NULL, 17 | rights_copy int(11) DEFAULT NULL, 18 | rights_start datetime DEFAULT NULL, 19 | rights_end datetime DEFAULT NULL, 20 | content_fk varchar(255) NOT NULL, 21 | lsd_status integer default 0, 22 | FOREIGN KEY(content_fk) REFERENCES content(id) 23 | ); -------------------------------------------------------------------------------- /dbmodel/sqlite_db_setup_lsdserver.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE license_status ( 2 | id INTEGER PRIMARY KEY, 3 | status int(11) NOT NULL, 4 | license_updated datetime NOT NULL, 5 | status_updated datetime NOT NULL, 6 | device_count int(11) DEFAULT NULL, 7 | potential_rights_end datetime DEFAULT NULL, 8 | license_ref varchar(255) NOT NULL, 9 | rights_end datetime DEFAULT NULL 10 | ); 11 | 12 | CREATE INDEX license_ref_index ON license_status (license_ref); 13 | 14 | CREATE TABLE event ( 15 | id integer PRIMARY KEY, 16 | device_name varchar(255) DEFAULT NULL, 17 | timestamp datetime NOT NULL, 18 | type int NOT NULL, 19 | device_id varchar(255) DEFAULT NULL, 20 | license_status_fk int NOT NULL, 21 | FOREIGN KEY(license_status_fk) REFERENCES license_status(id) 22 | ); 23 | 24 | CREATE INDEX license_status_fk_index on event (license_status_fk); -------------------------------------------------------------------------------- /dbmodel/sqlserver _db_setup_frontend.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE publication ( 2 |   id integer IDENTITY PRIMARY KEY, 3 |   uuid varchar(255) NOT NULL, 4 |   title varchar(255) NOT NULL, 5 |   status varchar(255) NOT NULL 6 | ); 7 | 8 | CREATE INDEX uuid_index ON publication (uuid); 9 | 10 | CREATE TABLE "user" ( 11 |   id integer IDENTITY PRIMARY KEY, 12 |   uuid varchar(255) NOT NULL, 13 |   name varchar(64) NOT NULL, 14 |   email varchar(64) NOT NULL, 15 |   password varchar(64) NOT NULL, 16 |   hint varchar(64) NOT NULL 17 | ); 18 | 19 | CREATE TABLE purchase ( 20 |   id integer IDENTITY PRIMARY KEY, 21 |   uuid varchar(255) NOT NULL, 22 |   publication_id integer NOT NULL, 23 |   user_id integer NOT NULL, 24 |   license_uuid varchar(255) NULL, 25 |   type varchar(32) NOT NULL, 26 | transaction_date datetime, 27 |   start_date datetime, 28 |   end_date datetime, 29 |   status varchar(255) NOT NULL, 30 |   FOREIGN KEY (publication_id) REFERENCES publication(id), 31 |   FOREIGN KEY (user_id) REFERENCES "user"(id) 32 | ); 33 |    34 | CREATE INDEX idx_purchase ON purchase (license_uuid); 35 | 36 | 37 | CREATE TABLE license_view ( 38 |   id integer IDENTITY PRIMARY KEY, 39 |   uuid varchar(255) NOT NULL, 40 |   device_count smallint NOT NULL, 41 |   status varchar(255) NOT NULL, 42 |   message varchar(255) NOT NULL 43 | ); 44 | -------------------------------------------------------------------------------- /dbmodel/sqlserver_db_setup_lcpserver.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE content ( 2 | id varchar(255) PRIMARY KEY NOT NULL, 3 | encryption_key varbinary(64) NOT NULL, 4 | location text NOT NULL, 5 | length int, 6 | sha256 varchar(64), 7 | type varchar(255) 8 | ); 9 | 10 | CREATE TABLE license ( 11 | id varchar(255) PRIMARY KEY NOT NULL, 12 | user_id varchar(255) NOT NULL, 13 | provider varchar(255) NOT NULL, 14 | issued datetime NOT NULL, 15 | updated datetime DEFAULT NULL, 16 | rights_print smallint DEFAULT NULL, 17 | rights_copy smallint DEFAULT NULL, 18 | rights_start datetime DEFAULT NULL, 19 | rights_end datetime DEFAULT NULL, 20 | content_fk varchar(255) NOT NULL, 21 | lsd_status tinyint default 0, 22 | FOREIGN KEY(content_fk) REFERENCES content(id) 23 | ); -------------------------------------------------------------------------------- /dbmodel/sqlserver_db_setup_lsdserver.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE license_status ( 2 | id integer IDENTITY PRIMARY KEY, 3 | status tinyint NOT NULL, 4 | license_updated datetime NOT NULL, 5 | status_updated datetime NOT NULL, 6 | device_count smallint DEFAULT NULL, 7 | potential_rights_end datetime DEFAULT NULL, 8 | license_ref varchar(255) NOT NULL, 9 | rights_end datetime DEFAULT NULL 10 | ); 11 | 12 | CREATE INDEX license_ref_index ON license_status (license_ref); 13 | 14 | CREATE TABLE event ( 15 | id integer IDENTITY PRIMARY KEY, 16 | device_name varchar(255) DEFAULT NULL, 17 | timestamp datetime NOT NULL, 18 | type int NOT NULL, 19 | device_id varchar(255) DEFAULT NULL, 20 | license_status_fk int NOT NULL, 21 | FOREIGN KEY(license_status_fk) REFERENCES license_status(id) 22 | ); 23 | 24 | CREATE INDEX license_status_fk_index on event (license_status_fk); -------------------------------------------------------------------------------- /dbutils/dbutils.go: -------------------------------------------------------------------------------- 1 | package dbutils 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | func getPostgresQuery(query string) string { 10 | var buffer bytes.Buffer 11 | idx := 1 12 | for _, char := range query { 13 | if char == '?' { 14 | buffer.WriteString(fmt.Sprintf("$%d", idx)) 15 | idx += 1 16 | } else { 17 | buffer.WriteRune(char) 18 | } 19 | } 20 | return buffer.String() 21 | } 22 | 23 | // GetParamQuery replaces parameter placeholders '?' in the SQL query to 24 | // placeholders supported by the selected database driver. 25 | func GetParamQuery(database, query string) string { 26 | if strings.HasPrefix(database, "postgres") { 27 | return getPostgresQuery(query) 28 | } else { 29 | return query 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dbutils/dbutils_test.go: -------------------------------------------------------------------------------- 1 | package dbutils 2 | 3 | import "testing" 4 | 5 | const demo_query = "SELECT * FROM test WHERE id = ? AND test = ? LIMIT 1" 6 | 7 | func TestGetParamQuery(t *testing.T) { 8 | q := GetParamQuery("postgres", demo_query) 9 | if q != "SELECT * FROM test WHERE id = $1 AND test = $2 LIMIT 1" { 10 | t.Fatalf("Incorrect postgres query") 11 | } 12 | 13 | q = GetParamQuery("sqlite3", demo_query) 14 | if q != "SELECT * FROM test WHERE id = ? AND test = ? LIMIT 1" { 15 | t.Fatalf("Incorrect sqlite3 query") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /encrypt/store_publication.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 encrypt 6 | 7 | import ( 8 | "errors" 9 | "os" 10 | "strings" 11 | 12 | "github.com/readium/readium-lcp-server/storage" 13 | ) 14 | 15 | // StoreS3Publication stores an encrypted file into its definitive storage. 16 | // Only called for S3 buckets. 17 | func StoreS3Publication(inputPath, storagePath, name string) error { 18 | 19 | s3Split := strings.Split(storagePath, ":") 20 | 21 | s3conf := storage.S3Config{} 22 | s3conf.Region = s3Split[1] 23 | s3conf.Bucket = s3Split[2] 24 | 25 | var store storage.Store 26 | // init the S3 storage 27 | store, err := storage.S3(s3conf) 28 | if err != nil { 29 | return errors.New("could not init the S3 storage") 30 | } 31 | 32 | // open the encrypted file, defer its deletion 33 | file, err := os.Open(inputPath) 34 | if err != nil { 35 | return err 36 | } 37 | defer cleanupTempFile(file) 38 | 39 | // add the file to the storage with the name passed as parameter 40 | _, err = store.Add(name, file) 41 | if err != nil { 42 | return err 43 | } 44 | return nil 45 | } 46 | 47 | // cleanupTempFile closes and deletes a temporary file 48 | func cleanupTempFile(f *os.File) { 49 | if f == nil { 50 | return 51 | } 52 | f.Close() 53 | os.Remove(f.Name()) 54 | } 55 | -------------------------------------------------------------------------------- /epub/epub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 European Digital Reading Lab. All rights reserved. 2 | // Licensed to the Readium Foundation under one or more contributor license agreements. 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 5 | 6 | package epub 7 | 8 | import ( 9 | "archive/zip" 10 | "io" 11 | "sort" 12 | "strings" 13 | 14 | "github.com/readium/readium-lcp-server/epub/opf" 15 | "github.com/readium/readium-lcp-server/xmlenc" 16 | ) 17 | 18 | const ( 19 | ContainerFile = "META-INF/container.xml" 20 | EncryptionFile = "META-INF/encryption.xml" 21 | LicenseFile = "META-INF/license.lcpl" 22 | 23 | ContentType_XHTML = "application/xhtml+xml" 24 | ContentType_HTML = "text/html" 25 | 26 | ContentType_NCX = "application/x-dtbncx+xml" 27 | 28 | ContentType_EPUB = "application/epub+zip" 29 | ) 30 | 31 | type Epub struct { 32 | Encryption *xmlenc.Manifest 33 | Package []opf.Package 34 | Resource []*Resource 35 | cleartextResources []string 36 | } 37 | 38 | func (ep Epub) Cover() (bool, *Resource) { 39 | 40 | for _, p := range ep.Package { 41 | 42 | var coverImageID string 43 | coverImageID = "cover-image" 44 | for _, meta := range p.Metadata.Metas { 45 | if meta.Name == "cover" { 46 | coverImageID = meta.Content 47 | } 48 | } 49 | 50 | for _, it := range p.Manifest.Items { 51 | 52 | if strings.Contains(it.Properties, "cover-image") || 53 | it.ID == coverImageID { 54 | 55 | // To be found later, resources in the EPUB root folder 56 | // must not be prefixed by "./" 57 | path := it.Href 58 | if p.BasePath != "." { 59 | path = p.BasePath + "/" + it.Href 60 | } 61 | for _, r := range ep.Resource { 62 | if r.Path == path { 63 | return true, r 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | return false, nil 71 | } 72 | 73 | func (ep *Epub) Add(name string, body io.Reader, size uint64) error { 74 | ep.Resource = append(ep.Resource, &Resource{Contents: body, StorageMethod: zip.Deflate, Path: name, OriginalSize: size}) 75 | 76 | return nil 77 | } 78 | 79 | type Resource struct { 80 | Path string 81 | ContentType string 82 | OriginalSize uint64 83 | ContentsSize uint64 84 | Compressed bool 85 | StorageMethod uint16 86 | Contents io.Reader 87 | } 88 | 89 | func (ep Epub) CanEncrypt(file string) bool { 90 | i := sort.SearchStrings(ep.cleartextResources, file) 91 | 92 | return i >= len(ep.cleartextResources) || ep.cleartextResources[i] != file 93 | } 94 | -------------------------------------------------------------------------------- /epub/opf/opf.go: -------------------------------------------------------------------------------- 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 opf 6 | 7 | import ( 8 | "encoding/xml" 9 | "io" 10 | 11 | "golang.org/x/net/html/charset" 12 | ) 13 | 14 | // Package is the main opf structure 15 | type Package struct { 16 | BasePath string `xml:"-"` 17 | Metadata Metadata `xml:"http://www.idpf.org/2007/opf metadata"` 18 | Manifest Manifest `xml:"http://www.idpf.org/2007/opf manifest"` 19 | } 20 | 21 | // Metadata is the package metadata structure 22 | type Metadata struct { 23 | Identifier string `json:"identifier" xml:"http://purl.org/dc/elements/1.1/ identifier"` 24 | Title []string `json:"title" xml:"http://purl.org/dc/elements/1.1/ title"` 25 | Description string `json:"description" xml:"http://purl.org/dc/elements/1.1/ description"` 26 | Date string `json:"date" xml:"http://purl.org/dc/elements/1.1/ date"` 27 | Author []string `json:"author" xml:"http://purl.org/dc/elements/1.1/ creator"` 28 | Contributor []string `json:"contributor" xml:"http://purl.org/dc/elements/1.1/ contributor"` 29 | Publisher []string `json:"publisher" xml:"http://purl.org/dc/elements/1.1/ publisher"` 30 | Language []string `json:"language" xml:"http://purl.org/dc/elements/1.1/ language"` 31 | Subject []string `json:"subject" xml:"http://purl.org/dc/elements/1.1/ subject"` 32 | Metas []Meta `xml:"http://www.idpf.org/2007/opf meta"` 33 | } 34 | 35 | // Meta is the metadata item structure 36 | type Meta struct { 37 | Name string `xml:"name,attr"` // EPUB 2 38 | Content string `xml:"content,attr"` 39 | Property string `xml:"property,attr"` // EPUB 3 40 | Refines string `xml:"refines,attr"` 41 | Text string `xml:",chardata"` 42 | } 43 | 44 | // Manifest is the package manifest structure 45 | type Manifest struct { 46 | Items []Item `xml:"http://www.idpf.org/2007/opf item"` 47 | } 48 | 49 | // Item is the manifest item structure 50 | type Item struct { 51 | ID string `xml:"id,attr"` 52 | Href string `xml:"href,attr"` 53 | MediaType string `xml:"media-type,attr"` 54 | Properties string `xml:"properties,attr"` 55 | } 56 | 57 | // ItemWithPath looks for the manifest item corresponding to a given path 58 | func (m Manifest) ItemWithPath(path string) (Item, bool) { 59 | for _, i := range m.Items { 60 | if i.Href == path { // FIXME(JPB) Canonicalize the path 61 | return i, true 62 | } 63 | } 64 | return Item{}, false 65 | } 66 | 67 | // Parse parses the opf xml struct and returns a Package object 68 | func Parse(r io.Reader) (Package, error) { 69 | var p Package 70 | xd := xml.NewDecoder(r) 71 | // deal with non utf-8 xml files 72 | xd.CharsetReader = charset.NewReaderLabel 73 | err := xd.Decode(&p) 74 | return p, err 75 | } 76 | -------------------------------------------------------------------------------- /epub/reader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package epub 27 | 28 | import ( 29 | "archive/zip" 30 | "fmt" 31 | "sort" 32 | "testing" 33 | ) 34 | 35 | func TestEpubLoading(t *testing.T) { 36 | 37 | zr, err := zip.OpenReader("../test/samples/sample.epub") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | defer zr.Close() 42 | 43 | ep, err := Read(&zr.Reader) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | if len(ep.Resource) == 0 { 49 | t.Error("Expected some resources") 50 | } 51 | 52 | if len(ep.Package) != 1 { 53 | t.Errorf("Expected 1 opf, got %d", len(ep.Package)) 54 | } 55 | 56 | expectedCleartext := []string{ContainerFile, "OPS/package.opf", "OPS/images/9780316000000.jpg", "OPS/toc.xhtml"} 57 | sort.Strings(expectedCleartext) 58 | if fmt.Sprintf("%v", ep.cleartextResources) != fmt.Sprintf("%v", expectedCleartext) { 59 | t.Errorf("Cleartext resources, expected %v, got %v", expectedCleartext, ep.cleartextResources) 60 | } 61 | 62 | if found, r := ep.Cover(); !found || r == nil { 63 | t.Error("Expected a cover to be found") 64 | } 65 | 66 | if expected := ContentType_XHTML; ep.Resource[2].ContentType != expected { 67 | t.Errorf("Content Type matching, expected %v, got %v", expected, ep.Resource[2].ContentType) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /epub/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package epub 27 | 28 | import ( 29 | "archive/zip" 30 | "io" 31 | ) 32 | 33 | func findFileInZip(zr *zip.Reader, path string) (*zip.File, error) { 34 | for _, f := range zr.File { 35 | if f.Name == path { 36 | return f, nil 37 | } 38 | } 39 | return nil, io.EOF 40 | } 41 | -------------------------------------------------------------------------------- /frontend/api/repository.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package staticapi 27 | 28 | import ( 29 | "encoding/json" 30 | "net/http" 31 | 32 | "github.com/readium/readium-lcp-server/api" 33 | "github.com/readium/readium-lcp-server/frontend/webrepository" 34 | "github.com/readium/readium-lcp-server/problem" 35 | ) 36 | 37 | // GetRepositoryMasterFiles returns a list of repository masterfiles 38 | func GetRepositoryMasterFiles(w http.ResponseWriter, r *http.Request, s IServer) { 39 | var err error 40 | 41 | files := make([]webrepository.RepositoryFile, 0) 42 | 43 | fn := s.RepositoryAPI().GetMasterFiles() 44 | 45 | for it, err := fn(); err == nil; it, err = fn() { 46 | files = append(files, it) 47 | } 48 | 49 | w.Header().Set("Content-Type", api.ContentType_JSON) 50 | 51 | enc := json.NewEncoder(w) 52 | err = enc.Encode(files) 53 | if err != nil { 54 | problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 55 | return 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /frontend/manage/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | # 4 spaces indentation 12 | [*.{ts,scss,css}] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | # 2 spaces indentation 17 | [*.{html}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.md] 22 | max_line_length = 0 23 | trim_trailing_whitespace = false 24 | 25 | # Indentation override 26 | #[lib/**.js] 27 | #[{package.json,.travis.yml}] 28 | #[**/**.js] 29 | -------------------------------------------------------------------------------- /frontend/manage/.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: node_js 4 | node_js: 5 | - "5" 6 | os: 7 | - linux 8 | env: 9 | global: 10 | - DBUS_SESSION_BUS_ADDRESS=/dev/null 11 | - DISPLAY=:99.0 12 | - CHROME_BIN=chromium-browser 13 | before_script: 14 | - sh -e /etc/init.d/xvfb start 15 | install: 16 | - npm install 17 | script: 18 | - npm run lint 19 | - npm run test-once 20 | - npm run e2e 21 | -------------------------------------------------------------------------------- /frontend/manage/LICENSE-angular-quickstart: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2016 Google, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/manage/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { PageNotFoundComponent } from './not-found.component'; 4 | 5 | const appRoutes: Routes = [ 6 | { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, 7 | { path: '**', component: PageNotFoundComponent } 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [ 12 | RouterModule.forRoot(appRoutes) 13 | ], 14 | exports: [ 15 | RouterModule 16 | ] 17 | }) 18 | 19 | export class AppRoutingModule {} 20 | -------------------------------------------------------------------------------- /frontend/manage/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
6 |
7 |
8 | 9 |
10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /frontend/manage/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from '@angular/core'; 2 | import { Subscription } from 'rxjs/Subscription'; 3 | 4 | import { SidebarService } from './shared/sidebar/sidebar.service'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'lcp-app', 9 | templateUrl: 'app.component.html' 10 | }) 11 | 12 | export class AppComponent implements OnDestroy { 13 | sidebarOpen: boolean = false; 14 | private sidebarSubscription: Subscription; 15 | 16 | constructor(private sidebarService: SidebarService) { 17 | this.sidebarSubscription = sidebarService.open$.subscribe( 18 | sidebarOpen => { 19 | this.sidebarOpen = sidebarOpen; 20 | } 21 | ); 22 | } 23 | 24 | ngOnDestroy() { 25 | // prevent memory leak when component destroyed 26 | this.sidebarSubscription.unsubscribe(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/manage/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpModule } from '@angular/http'; 4 | 5 | import { PageNotFoundComponent } from './not-found.component'; 6 | import { AppComponent } from './app.component'; 7 | import { AppRoutingModule } from './app-routing.module'; 8 | 9 | import { LsdModule } from './lsd/lsd.module'; 10 | import { SidebarModule } from './shared/sidebar/sidebar.module'; 11 | import { HeaderModule } from './shared/header/header.module'; 12 | import { DashboardModule } from './dashboard/dashboard.module'; 13 | import { UserModule } from './user/user.module'; 14 | import { PublicationModule } from './publication/publication.module'; 15 | import { PurchaseModule } from './purchase/purchase.module'; 16 | import { LicenseModule } from './license/license.module'; 17 | 18 | 19 | @NgModule({ 20 | imports: [ 21 | BrowserModule, 22 | HttpModule, 23 | LsdModule, 24 | HeaderModule, 25 | SidebarModule, 26 | DashboardModule, 27 | UserModule, 28 | PublicationModule, 29 | PurchaseModule, 30 | LicenseModule, 31 | AppRoutingModule 32 | ], 33 | declarations: [ 34 | AppComponent, 35 | PageNotFoundComponent 36 | ], 37 | bootstrap: [ 38 | AppComponent 39 | ] 40 | }) 41 | 42 | export class AppModule { } 43 | -------------------------------------------------------------------------------- /frontend/manage/app/components/app.component.html: -------------------------------------------------------------------------------- 1 |

Testing web site for License Server Protection and License Status Document

2 | -------------------------------------------------------------------------------- /frontend/manage/app/components/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | moduleId: module.id, 5 | selector: 'lcp-test-app', 6 | templateUrl: '/app/components/app.component.html' 7 | }) 8 | export class AppComponent { name = 'Lcp/lsd server test'; } 9 | -------------------------------------------------------------------------------- /frontend/manage/app/components/lsd-structs.ts: -------------------------------------------------------------------------------- 1 | 2 | export class Updated { 3 | license: Date; 4 | status: Date; 5 | } 6 | 7 | export class Link { 8 | rel: string; 9 | href: string; 10 | type: string; 11 | title: string; 12 | profile: string; 13 | templated: boolean; 14 | } 15 | 16 | export class PotentialRights { 17 | end: Date; 18 | } 19 | 20 | 21 | export class Event { 22 | name: string; // device name 23 | timestamp: Date; 24 | type: string; 25 | id: string; // device ID 26 | } 27 | 28 | export class LicenseStatus { 29 | id: string; 30 | status: string; 31 | updated: Updated; 32 | message: string; 33 | links: Link[]; 34 | device_count: number; 35 | potential_rights: PotentialRights; 36 | events: Event[]; 37 | } 38 | -------------------------------------------------------------------------------- /frontend/manage/app/components/partialLicense.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user'; 2 | 3 | export const BASIC_PROFILE = 'http://readium.org/lcp/basic-profile'; 4 | export const V1_PROFILE = 'http://readium.org/lcp/profile-1.0'; 5 | 6 | export const USERKEY_ALGO = 'http://www.w3.org/2001/04/xmlenc#sha256'; 7 | export const PROVIDER = 'http://edrlab.org'; 8 | 9 | export class Key { 10 | algorithm: string; 11 | } 12 | 13 | export class ContentKey extends Key { 14 | encrypted_value: any[] | undefined; 15 | } 16 | 17 | export class UserKey extends Key { 18 | text_hint: string; 19 | key_check: any[] | undefined; 20 | value: any[] | undefined; 21 | clear_value: string | undefined; 22 | } 23 | 24 | export class Encryption { 25 | profile: string; 26 | content_key: ContentKey | undefined; 27 | user_key: UserKey | undefined; 28 | } 29 | 30 | export class Link { 31 | rel: string; 32 | href: string; 33 | type: string | undefined; 34 | title: string | undefined; 35 | profile: string | undefined; 36 | templated: boolean | undefined; 37 | size: number | undefined; 38 | checksum: string | undefined; 39 | }; 40 | 41 | export class UserRights { 42 | print: number | undefined; 43 | copy: number | undefined; 44 | start: Date |undefined; 45 | end: Date |undefined; 46 | } 47 | 48 | export class UserInfo { 49 | id: string; 50 | email: string; 51 | name: string; 52 | encrypted: string[] | undefined; 53 | } 54 | 55 | 56 | export class PartialLicense { 57 | provider: string; // 'http://edrlab.org' 58 | user: UserInfo; // get it from user.user_id, user_email, ... 59 | encryption: Encryption; 60 | rights: UserRights | undefined; 61 | } 62 | 63 | export class PartialLicenseJSON extends PartialLicense { 64 | // function to encode / decode JSON string 65 | 66 | } 67 | -------------------------------------------------------------------------------- /frontend/manage/app/components/purchase.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Headers, Http } from '@angular/http'; 3 | import 'rxjs/add/operator/toPromise'; 4 | import { User } from './user'; 5 | import { Purchase } from './purchase'; 6 | 7 | declare var Config: any; // this comes from the autogenerated config.js file 8 | @Injectable() 9 | export class PurchaseService { 10 | private usersUrl = Config.frontend.url + '/users' ; 11 | // /users/{user_id}/purchases 12 | private headers = new Headers ({'Content-Type': 'application/json'}); 13 | 14 | constructor (private http: Http) { } 15 | getPurchases(user: User): Promise { 16 | return this.http.get(this.usersUrl + '/' + user.userID + '/purchases') 17 | .toPromise() 18 | .then(function (response) { 19 | let purchases: Purchase[] = []; 20 | for (let ResponseItem of response.json()) { 21 | let p = new Purchase; 22 | p.label = ResponseItem.label; 23 | p.licenseID = ResponseItem.licenseID; 24 | p.purchaseID = ResponseItem.purchaseID; 25 | p.resource = ResponseItem.resource; 26 | p.transactionDate = ResponseItem.transactionDate; 27 | p.user = ResponseItem.user; 28 | p.partialLicense = ResponseItem.partialLicense; 29 | purchases[purchases.length] = p; 30 | } 31 | return purchases; 32 | }) 33 | .catch(this.handleError); 34 | } 35 | 36 | create(purchase: Purchase): Promise { 37 | return this.http 38 | .put(this.usersUrl + '/' + purchase.user.userID + '/purchases', JSON.stringify(purchase), {headers: this.headers}) 39 | .toPromise() 40 | .then(function (response) { 41 | if ((response.status === 200) || (response.status === 201)) { 42 | return purchase; // ok 43 | } else { 44 | throw 'Error in create(purchase); ' + response.status + response.text; 45 | } 46 | }) 47 | .catch(this.handleError); 48 | } 49 | 50 | private handleError(error: any): Promise { 51 | console.error('An error occurred (purchase-service)', error); 52 | return Promise.reject(error.message || error); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /frontend/manage/app/components/purchase.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user'; 2 | 3 | export class Purchase { 4 | user: User; 5 | purchaseID: Number; 6 | resource: String; 7 | label: string; 8 | licenseID: string; 9 | transactionDate: Date; 10 | partialLicense: string; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/manage/app/components/purchases.css: -------------------------------------------------------------------------------- 1 | .purchase { 2 | border-color: darkgrey; 3 | background: black; 4 | color : white; 5 | padding: 8px 8px 8px 8px; 6 | margin: 16px 16px; 7 | } 8 | .rights,.register { 9 | font-style: italic; 10 | color : gray; 11 | width: 50%; 12 | margin-left: 32px; 13 | } 14 | 15 | .licenseID { 16 | font-size: 75%; 17 | width: 50%; 18 | margin-left: 32px; 19 | } 20 | 21 | .inputhours { 22 | width: 50px; 23 | } -------------------------------------------------------------------------------- /frontend/manage/app/components/purchases.html: -------------------------------------------------------------------------------- 1 |

Purchases

2 | 3 |
    4 |
  • 5 | {{purchase.transactionDate | date:'medium' }} 6 | 7 | 8 | 9 | {{purchase.label}}
    10 | {{purchase.licenseID}} 11 |
    12 | ID 13 | Name 14 | 15 |
    16 |
    17 |
    18 | {{ purchase.partialLicense | ShowRights }} 19 | 20 |
    21 |
    22 | 23 | 24 | 25 |
    26 |
    27 |
  • 28 |
-------------------------------------------------------------------------------- /frontend/manage/app/components/resource-list.html: -------------------------------------------------------------------------------- 1 |

Listing of LCP Resources (please choose one)

2 |
    3 |
  • 4 | {{resource.id}} {{resource.location}} 5 | {{resource.length}} {{resource.sha256}} 6 |
  • 7 |
8 |
9 |

{{selectedResource.location }} ( {{selectedResource.id }} )

10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /frontend/manage/app/components/resource.css: -------------------------------------------------------------------------------- 1 | .lcpResource { 2 | 3 | } 4 | .resources { 5 | padding: 16px; 6 | margin-left: 24px; 7 | } 8 | 9 | .selectedResource { 10 | position: absolute; 11 | top: 100px; 12 | right: 0; 13 | z-index: 0; 14 | width: 40%; 15 | float: right; 16 | 17 | background-color: blue; 18 | border-radius: 10px; 19 | border: 3px solid midnightblue; 20 | margin:8px 48px ; 21 | padding:8px; 22 | } 23 | 24 | .anID { 25 | font-size: 9px; 26 | color: silver; 27 | font-weight: bold; 28 | } 29 | .aLength { 30 | font-size: 9px; 31 | color: red; 32 | font-weight: bold; 33 | } 34 | .aSha256 { 35 | font-size: 9px; 36 | color: green; 37 | font-weight: bold; 38 | } 39 | 40 | 41 | .aLabel { 42 | 43 | } 44 | .lcpBuy { 45 | 46 | } 47 | .lcpLoan { 48 | 49 | } 50 | 51 | 52 | .inputhours { 53 | width: 50px; 54 | } -------------------------------------------------------------------------------- /frontend/manage/app/components/resource.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import 'rxjs/add/operator/toPromise'; 4 | import { Resource } from './resource'; 5 | 6 | declare var Config: any; // this comes from the autogenerated config.js file 7 | @Injectable() 8 | export class ResourceService { 9 | private resourceUrl = Config.lcp.url + '/contents'; 10 | 11 | constructor (private http: Http) { } 12 | getResources(): Promise { 13 | return this.http.get(this.resourceUrl) 14 | .toPromise() 15 | .then(function (response) { 16 | let resources: Resource[] = []; 17 | for (let jsonResult of response.json()) { 18 | resources[resources.length] = { 19 | id: jsonResult.id, location: jsonResult.location, length: jsonResult.length, sha256: jsonResult.sha356 20 | }; 21 | } 22 | return resources; 23 | }) 24 | .catch(this.handleError); 25 | } 26 | 27 | private handleError(error: any): Promise { 28 | console.error('An error occurred', error); 29 | return Promise.reject(error.message || error); 30 | } 31 | getUser(id: string): Promise { 32 | return this.getResources() 33 | .then(resources => resources.find(resource => resource.id === id)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/manage/app/components/resource.ts: -------------------------------------------------------------------------------- 1 | export class Resource { 2 | id: string; 3 | location: string; 4 | length: number; 5 | sha256: string | undefined | null ; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/manage/app/components/rightsFromPartialLicense.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import * as lic from './partialLicense'; 3 | 4 | /* 5 | * Return only the rights of a partial license (or undefined) 6 | * Takes partialLicense as a string argument 7 | * Usage: 8 | * partialLicense | filterRights 9 | */ 10 | @Pipe({name: 'FilterRights'}) 11 | export class FilterRights implements PipeTransform { 12 | 13 | transform(partialLicense: string): lic.UserRights | undefined { 14 | let r: lic.UserRights = new lic.UserRights; 15 | let obj: any; 16 | obj = JSON.parse(partialLicense, function (key, value): any { 17 | if (typeof value === 'string') { 18 | let a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 19 | if (a) { 20 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); 21 | } 22 | } 23 | return value; 24 | }); 25 | if ( obj.rights ) { 26 | console.log(obj.rights); 27 | r = obj.rights; 28 | return r; 29 | } 30 | return undefined; 31 | } 32 | } 33 | 34 | @Pipe({name: 'ShowRights'}) 35 | export class ShowRights implements PipeTransform { 36 | 37 | transform(partialLicense: string): string { 38 | let r: lic.UserRights = new lic.UserRights; 39 | let obj: any; 40 | obj = JSON.parse(partialLicense, function (key, value): any { 41 | if (typeof value === 'string') { 42 | let a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 43 | if (a) { 44 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); 45 | } 46 | } 47 | return value; 48 | }); 49 | if ( obj.rights ) { 50 | console.log(obj.rights); 51 | r = obj.rights; 52 | let s: string = ''; 53 | if ( r.copy >0 ) { 54 | s = 'copy=' + r.copy + ', print=' + r.print + ' '; 55 | } 56 | return s + 'available from ' + r.start.toLocaleString() + ' to ' + r.end.toLocaleString(); 57 | } 58 | return ''; 59 | } 60 | } -------------------------------------------------------------------------------- /frontend/manage/app/components/user-component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import {ActivatedRoute, Params} from '@angular/router'; 3 | import {Location} from '@angular/common'; 4 | 5 | import { User } from './user'; 6 | import { UserService } from './user.service'; 7 | 8 | @Component({ 9 | moduleId: module.id, 10 | selector: 'user', 11 | templateUrl: '/app/components/user.html', 12 | styleUrls: ['../../app/components/user.css'], 13 | providers: [UserService] 14 | }) 15 | 16 | export class UserComponent implements OnInit { 17 | @Input() user: User; 18 | @Input() newPassword: string; 19 | 20 | constructor( 21 | private userService: UserService, 22 | private route: ActivatedRoute, 23 | private location: Location 24 | ) {} 25 | 26 | ngOnInit(): void { 27 | this.route.params.forEach((params: Params) => { 28 | let id = +params['id']; 29 | this.userService.getUser(id) 30 | .then(user => this.user = user); 31 | }); 32 | } 33 | goBack(): void { 34 | this.location.back(); 35 | } 36 | 37 | save(): void { 38 | this.userService.update(this.user, this.newPassword) 39 | .then(() => this.goBack()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/manage/app/components/user-list-component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | import { Router } from '@angular/router'; 4 | import { User } from './user'; 5 | import { UserService } from './user.service'; 6 | 7 | 8 | @Component({ 9 | moduleId: module.id, 10 | selector: 'users', 11 | templateUrl: '/app/components/user-list.html', 12 | styleUrls: ['../../app/components/user.css'], 13 | providers: [UserService] 14 | }) 15 | 16 | 17 | export class UsersComponent implements OnInit { 18 | users: User[]; 19 | selectedUser: User; 20 | @Input() alias: string; 21 | @Input() email: string; 22 | @Input() password: string; 23 | 24 | constructor(private UserService: UserService, private router: Router) { } 25 | 26 | 27 | getUsers(): void { 28 | this.UserService.getUsers().then(Users => this.users = Users); 29 | } 30 | 31 | add(alias: string, email: string, password: string): void { 32 | email = email.trim(); 33 | if (!email) { return; }; 34 | this.UserService.create(alias, email, password) 35 | .then(User => { 36 | this.getUsers(); // refresh user list 37 | }); 38 | } 39 | 40 | delete(user: User): void { 41 | console.log('delete user ' + user.alias + ' ' + user.email + ' ' + user.userID); 42 | this.UserService 43 | .delete(user.userID) 44 | .then(() => { 45 | this.users = this.users.filter(h => h !== user ); 46 | if (this.selectedUser === user ) { 47 | this.selectedUser = null; 48 | } 49 | }); 50 | } 51 | 52 | ngOnInit(): void { 53 | this.getUsers(); 54 | } 55 | 56 | onSelect(User: User): void { 57 | this.selectedUser = User; 58 | } 59 | 60 | gotoDetail(): void { 61 | this.router.navigate(['/userdetail', this.selectedUser.userID]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /frontend/manage/app/components/user-list.html: -------------------------------------------------------------------------------- 1 |

1. Subscribe and/or select user in the list below

2 |
3 |

Add User

4 | 5 | 6 | 7 | 10 |
11 |

User list

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
{{user.userID}}{{user.alias}} hashed passphrase = {{user.password}}
20 |
21 |

2. Select item for license

22 |

Current user = {{selectedUser.alias | uppercase }}

23 | 24 |
25 | -------------------------------------------------------------------------------- /frontend/manage/app/components/user.css: -------------------------------------------------------------------------------- 1 | h2.user { 2 | font-size: 200%; 3 | } 4 | .users { 5 | width: 50%; 6 | margin-left:16px; 7 | } 8 | .anID { 9 | width: 50px; 10 | margin-left: 8px; 11 | padding: 8px; 12 | border-radius: 10px; 13 | border: 3px solid black; 14 | text-align: center; 15 | } 16 | .aLabel { 17 | width:200; 18 | color: black; 19 | background-color: gray; 20 | border-radius: 10px; 21 | border: 3px solid black; 22 | text-align: left; 23 | padding: 8px; 24 | } 25 | .selected { 26 | background-color: blue; 27 | } 28 | 29 | .delete { 30 | background-color: red; 31 | } 32 | -------------------------------------------------------------------------------- /frontend/manage/app/components/user.html: -------------------------------------------------------------------------------- 1 |
2 |

{{user.alias}}'s details!

3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | -------------------------------------------------------------------------------- /frontend/manage/app/components/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Headers, Http } from '@angular/http'; 3 | import 'rxjs/add/operator/toPromise'; 4 | import { User } from './user'; 5 | import * as jsSHA from 'jssha'; 6 | 7 | declare var Config: any; // this comes from the autogenerated config.js file 8 | @Injectable() 9 | export class UserService { 10 | private usersUrl = Config.frontend.url + '/users' ; 11 | private headers = new Headers ({'Content-Type': 'application/json'}); 12 | 13 | constructor (private http: Http) { } 14 | getUsers(): Promise { 15 | return this.http.get(this.usersUrl) 16 | .toPromise() 17 | .then(function (response) { 18 | let users: User[] = []; 19 | for (let jsonUser of response.json()) { 20 | users[users.length] = {userID: jsonUser.userID, alias: jsonUser.alias, email: jsonUser.email, password: jsonUser.password}; 21 | } 22 | return users; 23 | }) 24 | .catch(this.handleError); 25 | } 26 | 27 | create(newAlias: string, newEmail: string, newPassword: string): Promise { 28 | const jsSHAObject:jsSHA.jsSHA = new jsSHA("SHA-256","TEXT"); 29 | jsSHAObject.update(newPassword); 30 | let hashedPassword = jsSHAObject.getHash ("HEX"); 31 | let user: User = {userID: null, alias: newAlias, email: newEmail, password: hashedPassword}; 32 | return this.http 33 | .put(this.usersUrl, JSON.stringify(user), {headers: this.headers}) 34 | .toPromise() 35 | .then(function (response) { 36 | if (response.status === 201) { 37 | return user; 38 | } else { 39 | throw 'Error creating user ' + response.text; 40 | } 41 | }) 42 | .catch(this.handleError); 43 | } 44 | 45 | delete(id: number): Promise { 46 | const url = `${this.usersUrl}/${id}`; 47 | return this.http.delete(url, {headers: this.headers}) 48 | .toPromise() 49 | .then(() => null) 50 | .catch(this.handleError); 51 | } 52 | 53 | private handleError(error: any): Promise { 54 | console.error('An error occurred', error); 55 | return Promise.reject(error.message || error); 56 | } 57 | getUser(id: number): Promise { 58 | return this.getUsers() 59 | .then(users => users.find(user => user.userID === id)); 60 | } 61 | update(user: User, newPassword: string |undefined): Promise { 62 | if ((user.password != newPassword) && newPassword!=undefined) { 63 | const jsSHAObject:jsSHA.jsSHA = new jsSHA("SHA-256","TEXT"); 64 | jsSHAObject.update(newPassword); 65 | user.password = jsSHAObject.getHash ("HEX"); 66 | } 67 | const url = `${this.usersUrl}/${user.userID}`; 68 | return this.http 69 | .post(url, JSON.stringify(user), {headers: this.headers}) 70 | .toPromise() 71 | .then(() => user) 72 | .catch(this.handleError); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /frontend/manage/app/components/user.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | userID: number; 3 | alias: string; 4 | email: string; 5 | password: string | undefined | null ; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/manage/app/crud/crud-item.ts: -------------------------------------------------------------------------------- 1 | export interface CrudItem { 2 | id: any; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/manage/app/dashboard/dashboard-info.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Database Content

3 |
Publications {{infos.publicationCount}}
4 |
Users {{infos.userCount}}
5 |
Loaned {{infos.loanCount}}
6 |
Bought {{infos.buyCount}}
7 |
8 |
9 |

Best Sellers

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
TitleNbr
{{pub.title}}{{pub.count}}
22 |
23 | -------------------------------------------------------------------------------- /frontend/manage/app/dashboard/dashboard-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { DashboardInfo, DashboardBestSeller } from './dashboardInfo'; 3 | import { DashboardService } from './dashboard.service'; 4 | 5 | @Component({ 6 | moduleId: module.id, 7 | selector: 'lcp-frontend-dashboard-info', 8 | templateUrl: './dashboard-info.component.html' 9 | }) 10 | 11 | export class DashboardInfoComponent { 12 | infos: DashboardInfo; 13 | bestSellers: DashboardBestSeller[]; 14 | 15 | ngOnInit(): void { 16 | this.refreshInfos(); 17 | } 18 | 19 | constructor(private dashboardService: DashboardService) { 20 | 21 | } 22 | 23 | refreshInfos() 24 | { 25 | this.dashboardService.get().then( 26 | infos => { 27 | this.infos = infos; 28 | } 29 | ); 30 | this.dashboardService.getBestSeller().then( 31 | bestSellers => { 32 | this.bestSellers = bestSellers; 33 | } 34 | ); 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /frontend/manage/app/dashboard/dashboard-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { DashboardComponent } from './dashboard.component'; 5 | 6 | const dashboardRoutes: Routes = [ 7 | { path: 'dashboard', component: DashboardComponent } 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [ 12 | RouterModule.forChild(dashboardRoutes) 13 | ], 14 | exports: [ 15 | RouterModule 16 | ] 17 | }) 18 | 19 | export class DashboardRoutingModule { } 20 | -------------------------------------------------------------------------------- /frontend/manage/app/dashboard/dashboard.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/manage/app/dashboard/dashboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { DashboardService } from './dashboard.service' 3 | 4 | @Component({ 5 | moduleId: module.id, 6 | selector: 'lcp-frontend-dashboard', 7 | templateUrl: 'dashboard.component.html', 8 | providers: [DashboardService] 9 | }) 10 | 11 | export class DashboardComponent { } 12 | -------------------------------------------------------------------------------- /frontend/manage/app/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { DashboardRoutingModule } from './dashboard-routing.module'; 6 | import { DashboardComponent } from './dashboard.component'; 7 | import { DashboardInfoComponent } from './dashboard-info.component'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule, 12 | RouterModule, 13 | DashboardRoutingModule 14 | 15 | ], 16 | declarations: [ 17 | DashboardComponent, 18 | DashboardInfoComponent 19 | ] 20 | }) 21 | 22 | export class DashboardModule { } 23 | -------------------------------------------------------------------------------- /frontend/manage/app/dashboard/dashboard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Headers } from '@angular/http'; 3 | 4 | import 'rxjs/add/operator/toPromise'; 5 | 6 | import { DashboardInfo, DashboardBestSeller} from './dashboardInfo'; 7 | 8 | import { CrudService } from '../crud/crud.service'; 9 | 10 | declare var Config: any; // this comes from the autogenerated config.js file 11 | 12 | @Injectable() 13 | export class DashboardService{ 14 | private masterFileListUrl: string; 15 | private http:Http; 16 | private baseUrl: string; 17 | defaultHttpHeaders = new Headers({'Content-Type': 'application/json'}); 18 | 19 | 20 | constructor (http: Http) { 21 | this.http = http; 22 | this.baseUrl = Config.frontend.url; 23 | this.masterFileListUrl = Config.frontend.url + '/api/v1/repositories/master-files'; 24 | } 25 | 26 | 27 | decode(jsonObj: any): DashboardInfo { 28 | return { 29 | publicationCount: jsonObj.publicationCount, 30 | userCount: jsonObj.userCount, 31 | buyCount: jsonObj.buyCount, 32 | loanCount: jsonObj.loanCount 33 | } 34 | } 35 | 36 | get(): Promise { 37 | var self = this 38 | return this.http 39 | .get( 40 | this.baseUrl + "/dashboardInfos", 41 | { headers: this.defaultHttpHeaders }) 42 | .toPromise() 43 | .then(function (response) { 44 | let jsonObj = response.json(); 45 | return self.decode(jsonObj); 46 | }) 47 | .catch(this.handleError); 48 | } 49 | 50 | getBestSeller(): Promise { 51 | var self = this 52 | return this.http 53 | .get( 54 | this.baseUrl + "/dashboardBestSellers", 55 | { headers: this.defaultHttpHeaders }) 56 | .toPromise() 57 | .then(function (response) { 58 | if (response.ok) { 59 | let items: DashboardBestSeller[] = []; 60 | 61 | for (let jsonObj of response.json()) { 62 | items.push(jsonObj); 63 | } 64 | 65 | return items; 66 | } else { 67 | throw 'Error creating user ' + response.text; 68 | } 69 | }) 70 | .catch(this.handleError); 71 | } 72 | 73 | protected handleError(error: any): Promise { 74 | console.error('An error occurred', error); 75 | return Promise.reject(error.message || error); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /frontend/manage/app/dashboard/dashboardInfo.ts: -------------------------------------------------------------------------------- 1 | export class DashboardInfo{ 2 | publicationCount: number; 3 | userCount: number; 4 | buyCount: number; 5 | loanCount: number; 6 | } 7 | 8 | export class DashboardBestSeller{ 9 | title: number; 10 | count: string; 11 | } -------------------------------------------------------------------------------- /frontend/manage/app/license/license-info.component.html: -------------------------------------------------------------------------------- 1 |

Licenses

2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 14 | 19 | 24 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | 54 |
UUID 10 | Publication 11 | 12 | 13 | 15 | User 16 | 17 | 18 | 20 | Type 21 | 22 | 23 | 25 | Devices 26 | 27 | 28 | 30 | Statues 31 | 32 | 33 | Message
{{license.id}}{{license.publicationTitle}}{{license.userName}}{{license.type}}{{license.devices}}{{license.status}}{{license.message}} 47 | 50 | Status 51 | 52 |
55 |
56 | -------------------------------------------------------------------------------- /frontend/manage/app/license/license-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { License } from './license'; 3 | import { LicenseService } from './license.service'; 4 | 5 | declare var Config: any; 6 | 7 | @Component({ 8 | moduleId: module.id, 9 | selector: 'lcp-frontend-license-info', 10 | templateUrl: './license-info.component.html' 11 | }) 12 | 13 | 14 | export class LicenseInfoComponent { 15 | @Input('filterBox') filterBox: any; 16 | 17 | licenses: License[]; 18 | filter: number = 0; 19 | filtred = false; 20 | baseUrl: string; 21 | 22 | reverse: boolean = false; 23 | order: string; 24 | 25 | 26 | 27 | ngOnInit(): void { 28 | this.baseUrl = Config.frontend.url; 29 | this.refreshInfos(); 30 | 31 | this.order = "publicationTitle"; 32 | } 33 | 34 | constructor(private licenseService: LicenseService) { 35 | 36 | } 37 | 38 | onSubmit(){ 39 | this.filtred = true; 40 | this.refreshInfos(); 41 | } 42 | 43 | refreshInfos() 44 | { 45 | this.licenseService.get(this.filter).then( 46 | infos => { 47 | this.licenses = infos; 48 | } 49 | ); 50 | } 51 | 52 | orderBy(newOrder: string) 53 | { 54 | if (newOrder == this.order) 55 | { 56 | this.reverse = !this.reverse; 57 | } 58 | else 59 | { 60 | this.reverse = false; 61 | this.order = newOrder 62 | } 63 | } 64 | 65 | keyPressed(key:number) 66 | { 67 | if (key == 13) 68 | { 69 | this.onSubmit() 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /frontend/manage/app/license/license-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { LicenseComponent } from './license.component'; 5 | 6 | const licenseRoutes: Routes = [ 7 | { path: 'licenses', component: LicenseComponent } 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [ 12 | RouterModule.forChild(licenseRoutes) 13 | ], 14 | exports: [ 15 | RouterModule 16 | ] 17 | }) 18 | 19 | export class LicenseRoutingModule { } 20 | -------------------------------------------------------------------------------- /frontend/manage/app/license/license.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/manage/app/license/license.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { LicenseService } from './license.service' 3 | 4 | @Component({ 5 | moduleId: module.id, 6 | selector: 'lcp-frontend-license', 7 | templateUrl: 'license.component.html', 8 | providers: [LicenseService] 9 | }) 10 | 11 | export class LicenseComponent { } 12 | -------------------------------------------------------------------------------- /frontend/manage/app/license/license.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { LicenseService } from './license.service'; 6 | import { LicenseRoutingModule } from './license-routing.module'; 7 | import { LicenseComponent } from './license.component'; 8 | import { LicenseInfoComponent } from './license-info.component'; 9 | 10 | import { SortModule } from '../shared/pipes/sort.module'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | CommonModule, 15 | RouterModule, 16 | LicenseRoutingModule, 17 | SortModule 18 | ], 19 | declarations: [ 20 | LicenseComponent, 21 | LicenseInfoComponent 22 | ], 23 | providers: [ 24 | LicenseService 25 | ] 26 | }) 27 | 28 | export class LicenseModule { } 29 | -------------------------------------------------------------------------------- /frontend/manage/app/license/license.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Headers } from '@angular/http'; 3 | 4 | import 'rxjs/add/operator/toPromise'; 5 | 6 | import { License } from './license'; 7 | import { CrudService } from '../crud/crud.service'; 8 | 9 | declare var Config: any; // this comes from the autogenerated config.js file 10 | 11 | @Injectable() 12 | export class LicenseService{ 13 | private masterFileListUrl: string; 14 | private http:Http; 15 | private baseUrl: string; 16 | private lsdUrl: string; 17 | defaultHttpHeaders = new Headers({'Content-Type': 'application/json'}); 18 | 19 | 20 | constructor (http: Http) { 21 | this.http = http; 22 | this.baseUrl = Config.frontend.url; 23 | this.lsdUrl = Config.lsd.url 24 | this.masterFileListUrl = Config.frontend.url + '/api/v1/repositories/master-files'; 25 | } 26 | 27 | 28 | decode(jsonObj: any): License { 29 | return { 30 | id: jsonObj.ID, 31 | publicationTitle: jsonObj.publication_title, 32 | userName: jsonObj.user_name, 33 | type: jsonObj.type, 34 | devices: jsonObj.device_count, 35 | status: jsonObj.status, 36 | purchaseID: jsonObj.purchase_id 37 | } 38 | } 39 | 40 | get(devices: number): Promise { 41 | var self = this 42 | var headers: Headers = new Headers; 43 | return this.http 44 | .get( 45 | this.baseUrl + "/api/v1/licenses?devices=" + devices, 46 | { headers: this.defaultHttpHeaders }) 47 | .toPromise() 48 | .then(function (response) { 49 | let items: License[] = []; 50 | 51 | for (let jsonObj of response.json()) { 52 | items.push(self.decode(jsonObj)); 53 | } 54 | return items; 55 | }) 56 | .catch(this.handleError); 57 | } 58 | 59 | protected handleError(error: any): Promise { 60 | console.error('An error occurred', error); 61 | return Promise.reject(error.message || error); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /frontend/manage/app/license/license.ts: -------------------------------------------------------------------------------- 1 | import { Publication } from '../publication/publication'; 2 | import { User } from '../user/user'; 3 | 4 | export class License{ 5 | id : string 6 | publicationTitle: string 7 | userName : string 8 | type: string 9 | devices: number 10 | status: string 11 | purchaseID: string 12 | } -------------------------------------------------------------------------------- /frontend/manage/app/lsd/license-status.ts: -------------------------------------------------------------------------------- 1 | export class Updated { 2 | license: Date; 3 | status: Date; 4 | } 5 | 6 | export class Link { 7 | rel: string; 8 | href: string; 9 | type: string; 10 | title: string; 11 | profile: string; 12 | templated: boolean; 13 | } 14 | 15 | export class PotentialRights { 16 | end: Date; 17 | } 18 | 19 | export class Event { 20 | name: string; // device name 21 | timestamp: Date; 22 | type: string; 23 | id: string; // device ID 24 | } 25 | 26 | export class LicenseStatus { 27 | id: string; 28 | status: string; 29 | updated: Updated; 30 | message: string; 31 | links: Link[]; 32 | device_count: number; 33 | potential_rights: PotentialRights; 34 | events: Event[]; 35 | } 36 | -------------------------------------------------------------------------------- /frontend/manage/app/lsd/lsd.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { 5 | FormsModule, 6 | ReactiveFormsModule } from '@angular/forms'; 7 | 8 | import { Ng2DatetimePickerModule } from 'ng2-datetime-picker'; 9 | 10 | import { LsdService } from './lsd.service'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | CommonModule 15 | ], 16 | declarations: [], 17 | providers: [ 18 | LsdService 19 | ] 20 | }) 21 | 22 | export class LsdModule { } 23 | -------------------------------------------------------------------------------- /frontend/manage/app/lsd/lsd.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Headers } from '@angular/http'; 3 | 4 | import 'rxjs/add/operator/toPromise'; 5 | 6 | import { LicenseStatus } from './license-status' 7 | 8 | declare var Config: any; // this comes from the autogenerated config.js file 9 | 10 | @Injectable() 11 | export class LsdService { 12 | defaultHttpHeaders = new Headers( 13 | {'Content-Type': 'application/json'}); 14 | baseUrl: string = Config.lsd.url; 15 | 16 | constructor (private http: Http) { 17 | } 18 | 19 | get(id: string): Promise { 20 | let url = this.baseUrl + "/licenses/" + id + "/status"; 21 | return this.http 22 | .get( 23 | url, 24 | { headers: this.defaultHttpHeaders }) 25 | .toPromise() 26 | .then(function (response) { 27 | if (response.ok) { 28 | let jsonObj = response.json(); 29 | let licenseStatus = jsonObj as LicenseStatus; 30 | return licenseStatus; 31 | } else { 32 | throw 'Error retrieving license ' + response.text(); 33 | } 34 | }) 35 | .catch(this.handleError); 36 | } 37 | 38 | protected handleError(error: any): Promise { 39 | console.error('An error occurred', error); 40 | return Promise.reject(error.message || error); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/manage/app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); 6 | -------------------------------------------------------------------------------- /frontend/manage/app/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: '

Page not found

' 5 | }) 6 | 7 | export class PageNotFoundComponent {} 8 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/master-file.ts: -------------------------------------------------------------------------------- 1 | export class MasterFile { 2 | name: string; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication-add.component.html: -------------------------------------------------------------------------------- 1 |

Add publication

2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication-add.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | moduleId: module.id, 5 | selector: 'lcp-publication-add', 6 | templateUrl: 'publication-add.component.html' 7 | }) 8 | 9 | export class PublicationAddComponent {} 10 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication-edit.component.html: -------------------------------------------------------------------------------- 1 |

Edit publication

2 | 3 |
4 | 8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Params } from '@angular/router'; 3 | import 'rxjs/add/operator/switchMap'; 4 | 5 | import { Publication } from './publication'; 6 | import { PublicationService } from './publication.service'; 7 | 8 | @Component({ 9 | moduleId: module.id, 10 | selector: 'lcp-publication-edit', 11 | templateUrl: 'publication-edit.component.html' 12 | }) 13 | 14 | export class PublicationEditComponent implements OnInit { 15 | publication: Publication; 16 | 17 | constructor( 18 | private route: ActivatedRoute, 19 | private publicationService: PublicationService) { 20 | } 21 | 22 | ngOnInit(): void { 23 | this.route.params 24 | .switchMap((params: Params) => this.publicationService.get(""+params['id'])) 25 | .subscribe(publication => { 26 | this.publication = publication 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 11 |
12 |
13 | 18 |
19 |
20 | 21 |
22 |
27 |
{{droppedItem.file.name}}
28 |
Drop a publication here
29 |
The file must have an .epub, .pdf, .lpf, .webpub, .audiobook or .divina extension.
30 |
31 |
32 |
33 | 41 |
42 |
43 |
44 | 45 | 48 |
49 | 50 | 51 |
52 | 53 |
54 |
55 |

A publication with this name already exists. Shall we replace it with this new file?

56 | 57 |
58 |
59 | {{errorMessage}} 60 |
61 |
62 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication-list.component.html: -------------------------------------------------------------------------------- 1 |

Publications

2 | 3 |
4 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 48 | 49 | 50 |
15 | # 16 | 17 | 18 | 20 | Title 21 | 22 | 23 | 25 | UUID 26 | 27 | 28 | Actions
{{publication.id}}{{publication.title}}{{publication.uuid}} 38 | 43 | 45 | Edit 46 | 47 |
51 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Publication } from './publication'; 3 | import { PublicationService } from './publication.service'; 4 | import { Pipe } from "@angular/core"; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'lcp-publication-list', 9 | templateUrl: 'publication-list.component.html', 10 | }) 11 | 12 | export class PublicationListComponent implements OnInit { 13 | publications: Publication[]; 14 | search: string = ""; 15 | order: string; 16 | reverse: boolean = false; 17 | 18 | constructor(private publicationService: PublicationService) { 19 | this.publications = []; 20 | this.order = "id"; 21 | this.reverse = true; 22 | } 23 | 24 | refreshPublications(): void { 25 | this.publicationService.list().then( 26 | publications => { 27 | this.publications = publications; 28 | } 29 | ); 30 | } 31 | 32 | orderBy(newOrder: string) 33 | { 34 | if (newOrder == this.order) 35 | { 36 | this.reverse = !this.reverse; 37 | } 38 | else 39 | { 40 | this.reverse = false; 41 | this.order = newOrder 42 | } 43 | } 44 | 45 | keptWithFilter (pub :{title: string}): boolean 46 | { 47 | if (pub.title.toUpperCase().includes(this.search.toUpperCase())) 48 | { 49 | return true; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | ngOnInit(): void { 56 | this.refreshPublications(); 57 | } 58 | 59 | onRemove(objId: any): void { 60 | this.publicationService.delete(objId).then( 61 | publication => { 62 | this.refreshPublications(); 63 | } 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { PublicationEditComponent } from './publication-edit.component'; 5 | import { PublicationAddComponent } from './publication-add.component'; 6 | import { PublicationListComponent } from './publication-list.component'; 7 | 8 | const publicationRoutes: Routes = [ 9 | { path: 'publications/:id/edit', component: PublicationEditComponent }, 10 | { path: 'publications/add', component: PublicationAddComponent }, 11 | { path: 'publications', component: PublicationListComponent } 12 | ]; 13 | 14 | @NgModule({ 15 | imports: [ 16 | RouterModule.forChild(publicationRoutes) 17 | ], 18 | exports: [ 19 | RouterModule 20 | ] 21 | }) 22 | 23 | export class PublicationRoutingModule { } 24 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA, Directive } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { 5 | FormsModule, 6 | ReactiveFormsModule } from '@angular/forms'; 7 | 8 | import { PublicationService } from './publication.service'; 9 | import { PublicationRoutingModule } from './publication-routing.module'; 10 | import { PublicationAddComponent } from './publication-add.component'; 11 | import { PublicationEditComponent } from './publication-edit.component'; 12 | import { PublicationListComponent } from './publication-list.component'; 13 | import { PublicationFormComponent } from './publication-form.component'; 14 | import { FileUploadModule } from 'ng2-file-upload'; 15 | import { SortModule } from '../shared/pipes/sort.module'; 16 | 17 | @NgModule({ 18 | imports: [ 19 | CommonModule, 20 | RouterModule, 21 | FormsModule, 22 | ReactiveFormsModule, 23 | PublicationRoutingModule, 24 | FileUploadModule, 25 | SortModule 26 | ], 27 | declarations: [ 28 | PublicationAddComponent, 29 | PublicationEditComponent, 30 | PublicationListComponent, 31 | PublicationFormComponent 32 | ], 33 | providers: [ 34 | PublicationService 35 | ], 36 | schemas: [ CUSTOM_ELEMENTS_SCHEMA ] 37 | }) 38 | 39 | export class PublicationModule { } 40 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | 4 | import 'rxjs/add/operator/toPromise'; 5 | 6 | import { Publication } from './publication'; 7 | import { MasterFile } from './master-file'; 8 | 9 | import { CrudService } from '../crud/crud.service'; 10 | 11 | declare var Config: any; // this comes from the autogenerated config.js file 12 | 13 | @Injectable() 14 | export class PublicationService extends CrudService { 15 | private masterFileListUrl: string; 16 | 17 | constructor (http: Http) { 18 | super(); 19 | this.http = http; 20 | this.baseUrl = Config.frontend.url + '/api/v1/publications'; 21 | this.masterFileListUrl = Config.frontend.url + 22 | '/api/v1/repositories/master-files'; 23 | } 24 | 25 | decode(jsonObj: any): Publication { 26 | return { 27 | id: jsonObj.id, 28 | uuid: jsonObj.uuid, 29 | title: jsonObj.title, 30 | status: jsonObj.status, 31 | masterFilename: null 32 | } 33 | } 34 | 35 | encode(obj: Publication): any { 36 | return { 37 | id: obj.id, 38 | title: obj.title, 39 | masterFilename: obj.masterFilename 40 | } 41 | } 42 | 43 | checkByName(name: string): Promise { 44 | var self = this 45 | return this.http 46 | .get( 47 | this.baseUrl + "/check-by-title?title=" + name, 48 | { headers: this.defaultHttpHeaders }) 49 | .toPromise() 50 | .then(function (response) { 51 | let jsonObj = response.json(); 52 | return jsonObj; 53 | }) 54 | .catch(this.handleError); 55 | } 56 | 57 | getMasterFiles(): Promise { 58 | return this.http 59 | .get( 60 | this.masterFileListUrl, 61 | { headers: this.defaultHttpHeaders }) 62 | .toPromise() 63 | .then(function (response) { 64 | if (response.ok) { 65 | let items: MasterFile[] = []; 66 | 67 | for (let jsonObj of response.json()) { 68 | items.push({ 69 | name: jsonObj.name 70 | }); 71 | } 72 | 73 | return items; 74 | } else { 75 | throw 'Error creating user ' + response.text; 76 | } 77 | }) 78 | .catch(this.handleError); 79 | } 80 | 81 | addPublication(pub: Publication): Promise { 82 | return this.http 83 | .post( 84 | this.baseUrl, 85 | this.encode(pub), 86 | { headers: this.defaultHttpHeaders }) 87 | .toPromise() 88 | .then(function (response) { 89 | if (response.ok) { 90 | return 200; 91 | } else { 92 | throw 'Error creating publication ' + response.text; 93 | } 94 | }) 95 | .catch(this.handleAddError); 96 | } 97 | 98 | protected handleAddError(error: any): any { 99 | return error.status; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /frontend/manage/app/publication/publication.ts: -------------------------------------------------------------------------------- 1 | import { CrudItem } from '../crud/crud-item'; 2 | 3 | export class Publication implements CrudItem { 4 | id: number; 5 | uuid: string; 6 | title: string; 7 | masterFilename?: string; 8 | status?: string; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase-add.component.html: -------------------------------------------------------------------------------- 1 |

Add purchase

2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase-add.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | moduleId: module.id, 5 | selector: 'lcp-purchase-add', 6 | templateUrl: 'purchase-add.component.html' 7 | }) 8 | 9 | export class PurchaseAddComponent {} 10 | -------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase-edit.component.html: -------------------------------------------------------------------------------- 1 |

Renew purchase

2 | 3 |
4 | 8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Params } from '@angular/router'; 3 | import 'rxjs/add/operator/switchMap'; 4 | 5 | import { Purchase } from './purchase'; 6 | import { PurchaseService } from './purchase.service'; 7 | 8 | @Component({ 9 | moduleId: module.id, 10 | selector: 'lcp-purchase-edit', 11 | templateUrl: 'purchase-edit.component.html' 12 | }) 13 | 14 | export class PurchaseEditComponent implements OnInit { 15 | purchase: Purchase; 16 | 17 | constructor( 18 | private route: ActivatedRoute, 19 | private purchaseService: PurchaseService) { 20 | } 21 | 22 | ngOnInit(): void { 23 | this.route.params 24 | .switchMap((params: Params) => this.purchaseService.get(params['id'])) 25 | .subscribe(purchase => { 26 | this.purchase = purchase 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 13 |
14 | 15 |
16 | 17 | 26 |
27 | 28 |
29 | 30 |
31 | 36 |
37 |
38 | 43 |
44 |
45 | 46 |
47 | 48 |
49 | 54 |
55 |
56 | 61 |
62 |
63 | 64 |
65 | 66 | 69 |
70 | 71 | 72 | 73 |
74 | -------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable, Subscription } from 'rxjs/Rx'; 3 | 4 | import { Slug } from 'ng2-slugify'; 5 | import * as moment from 'moment'; 6 | import * as saveAs from 'file-saver'; 7 | 8 | import { Purchase } from './purchase'; 9 | import { PurchaseService } from './purchase.service'; 10 | import { Publication } from '../publication/publication'; 11 | import { User } from '../user/user'; 12 | 13 | declare var Config: any; // this comes from the autogenerated config.js file 14 | 15 | @Component({ 16 | moduleId: module.id, 17 | selector: 'lcp-purchase-list', 18 | templateUrl: 'purchase-list.component.html' 19 | }) 20 | 21 | export class PurchaseListComponent implements OnInit { 22 | purchases: Purchase[]; 23 | search: string = ""; 24 | order: string; 25 | reverse: boolean = false; 26 | private slug = new Slug('default'); 27 | 28 | constructor(private purchaseService: PurchaseService) { 29 | this.purchases = []; 30 | this.order = "id"; 31 | this.reverse = true; 32 | } 33 | 34 | refreshPurchases(): void { 35 | this.purchaseService.list().then( 36 | purchases => { 37 | this.purchases = purchases; 38 | } 39 | ); 40 | } 41 | 42 | orderBy(newOrder: string) 43 | { 44 | if (newOrder == this.order) 45 | { 46 | this.reverse = !this.reverse; 47 | } 48 | else 49 | { 50 | this.reverse = false; 51 | this.order = newOrder; 52 | } 53 | } 54 | 55 | keptWithFilter (pur: {publication: Publication, user: User}): boolean 56 | { 57 | if (pur.publication.title.toUpperCase().includes(this.search.toUpperCase()) || pur.user.name.toUpperCase().includes(this.search.toUpperCase())) 58 | { 59 | return true; 60 | } 61 | 62 | return false; 63 | } 64 | 65 | buildLicenseDeliveredClass(licenseUuid: string) { 66 | if (licenseUuid == null) { 67 | return "danger"; 68 | } 69 | 70 | return "success"; 71 | } 72 | 73 | buildStatusClass(status: string) { 74 | if (status == "error") { 75 | return "danger"; 76 | } else if (status == "returned") { 77 | return "warning" 78 | } 79 | return "success"; 80 | } 81 | 82 | formatDate(date: string): string { 83 | return moment(date).format('YYYY-MM-DD HH:mm'); 84 | } 85 | 86 | ngOnInit(): void { 87 | this.refreshPurchases(); 88 | } 89 | 90 | onRemove(objId: any): void { 91 | this.purchaseService.delete(objId).then( 92 | purchase => { 93 | this.refreshPurchases(); 94 | } 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { PurchaseAddComponent } from './purchase-add.component'; 5 | import { PurchaseEditComponent } from './purchase-edit.component'; 6 | import { PurchaseStatusComponent } from './purchase-status.component'; 7 | import { PurchaseListComponent } from './purchase-list.component'; 8 | 9 | const purchaseRoutes: Routes = [ 10 | { path: 'purchases/:id/renew', component: PurchaseEditComponent }, 11 | { path: 'purchases/:id/status', component: PurchaseStatusComponent }, 12 | { path: 'purchases/add', component: PurchaseAddComponent }, 13 | { path: 'purchases', component: PurchaseListComponent } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | RouterModule.forChild(purchaseRoutes) 19 | ], 20 | exports: [ 21 | RouterModule 22 | ] 23 | }) 24 | 25 | export class PurchaseRoutingModule { } 26 | -------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase-status.component.html: -------------------------------------------------------------------------------- 1 |

Purchase status

2 | 3 |
4 | 8 |

{{purchase.publication.title}}

9 | 16 | 17 | 21 | Renew 22 | 23 | 31 | 38 | 39 |

Date : {{formatDate(purchase.transactionDate)}}

40 |

User : {{purchase.user.name}}

41 |

Type : {{purchase.type}}

42 |

Start Date : {{formatDate(purchase.startDate)}}

43 |

End Date : {{formatDate(purchase.endDate)}}

44 |

Delivered : {{purchase.licenseUuid != null}}

45 |

46 |
47 |
48 |
49 | 50 | 51 |
52 |
53 | 54 | 55 |
56 |
57 |
58 | 59 |
60 |

Best Sellers

61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
IdNbr
{{}}{{}}
73 |
74 |
75 |
76 |
{{revokeMessage}}
-------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { 5 | FormsModule, 6 | ReactiveFormsModule } from '@angular/forms'; 7 | 8 | import { Ng2DatetimePickerModule } from 'ng2-datetime-picker'; 9 | 10 | import { PurchaseService } from './purchase.service'; 11 | import { PurchaseRoutingModule } from './purchase-routing.module'; 12 | import { PurchaseListComponent } from './purchase-list.component'; 13 | import { PurchaseFormComponent } from './purchase-form.component'; 14 | import { PurchaseEditComponent } from './purchase-edit.component'; 15 | import { PurchaseStatusComponent } from './purchase-status.component'; 16 | import { PurchaseAddComponent } from './purchase-add.component'; 17 | import { SortModule } from '../shared/pipes/sort.module'; 18 | 19 | 20 | @NgModule({ 21 | imports: [ 22 | CommonModule, 23 | RouterModule, 24 | FormsModule, 25 | ReactiveFormsModule, 26 | PurchaseRoutingModule, 27 | Ng2DatetimePickerModule, 28 | SortModule 29 | ], 30 | declarations: [ 31 | PurchaseListComponent, 32 | PurchaseFormComponent, 33 | PurchaseEditComponent, 34 | PurchaseStatusComponent, 35 | PurchaseAddComponent 36 | ], 37 | providers: [ 38 | PurchaseService 39 | ] 40 | }) 41 | 42 | export class PurchaseModule { } 43 | -------------------------------------------------------------------------------- /frontend/manage/app/purchase/purchase.ts: -------------------------------------------------------------------------------- 1 | import { CrudItem } from '../crud/crud-item'; 2 | import { Publication } from '../publication/publication'; 3 | import { User } from '../user/user'; 4 | 5 | export class Purchase implements CrudItem { 6 | id: number; 7 | uuid: string; 8 | publication: Publication; 9 | user: User; 10 | type: string; 11 | endDate: string; 12 | transactionDate?: string; 13 | startDate?: string; 14 | licenseUuid?: string | null; 15 | status?: string; 16 | maxEndDate?: string; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/manage/app/shared/header/header.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /frontend/manage/app/shared/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | moduleId: module.id, 5 | selector: 'lcp-header', 6 | templateUrl: 'header.component.html' 7 | }) 8 | 9 | export class HeaderComponent { } 10 | -------------------------------------------------------------------------------- /frontend/manage/app/shared/header/header.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { HeaderComponent } from './header.component'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | CommonModule 9 | ], 10 | declarations: [ 11 | HeaderComponent 12 | ], 13 | exports: [ 14 | HeaderComponent 15 | ] 16 | }) 17 | 18 | export class HeaderModule { } 19 | -------------------------------------------------------------------------------- /frontend/manage/app/shared/pipes/sort.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Sort } from "./sort.pipe" 3 | 4 | @NgModule({ 5 | imports: [], 6 | 7 | declarations: [Sort], 8 | 9 | exports: [Sort] 10 | }) 11 | 12 | export class SortModule { } 13 | -------------------------------------------------------------------------------- /frontend/manage/app/shared/pipes/sort.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({name: 'sortBy'}) 4 | export class Sort implements PipeTransform { 5 | transform(value: {}[], filter: string, reverse: boolean): any { 6 | var values: {a:string, b:string}; 7 | 8 | var getValue = (a:any, b:any, filter:string):{a:string, b:string} => 9 | { 10 | var newA:string; 11 | var newB:string; 12 | 13 | var newFilter: string[] = filter.split(".") 14 | for (var i = 0; i <= newFilter.length-1; i++) 15 | { 16 | a = a[newFilter[i]]; 17 | b = b[newFilter[i]]; 18 | } 19 | 20 | return { 21 | a, 22 | b 23 | } 24 | } 25 | 26 | if(!reverse){ 27 | value.sort(function (a:{}, b:{}) { 28 | values = getValue(a,b,filter); 29 | if (values.a == values.b) return 0; 30 | else if (values.a > values.b || values.a == null) return 1; 31 | else if (values.a < values.b || values.b == null) return -1; 32 | }); 33 | } else { 34 | 35 | value.sort(function (a:{}, b:{}) { 36 | values = getValue(a,b,filter); 37 | if (values.a == values.b) return 0; 38 | else if (values.a < values.b || values.a == null) return 1; 39 | else if (values.a > values.b || values.b == null) return -1; 40 | }); 41 | 42 | } 43 | return value; 44 | 45 | 46 | } 47 | 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /frontend/manage/app/shared/sidebar/sidebar.component.html: -------------------------------------------------------------------------------- 1 | 2 | 31 | -------------------------------------------------------------------------------- /frontend/manage/app/shared/sidebar/sidebar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SidebarService } from './sidebar.service'; 3 | 4 | @Component({ 5 | moduleId: module.id, 6 | selector: 'lcp-sidebar', 7 | templateUrl: 'sidebar.component.html' 8 | }) 9 | 10 | export class SidebarComponent { 11 | constructor(private sidebarService: SidebarService) { 12 | } 13 | 14 | toggle() { 15 | this.sidebarService.toggle(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/manage/app/shared/sidebar/sidebar.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { SidebarService } from './sidebar.service'; 6 | import { SidebarComponent } from './sidebar.component'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | CommonModule, 11 | RouterModule 12 | ], 13 | declarations: [ 14 | SidebarComponent 15 | ], 16 | exports: [ 17 | SidebarComponent 18 | ], 19 | providers: [ 20 | SidebarService 21 | ] 22 | }) 23 | 24 | export class SidebarModule { } 25 | -------------------------------------------------------------------------------- /frontend/manage/app/shared/sidebar/sidebar.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Subject } from 'rxjs/Subject'; 3 | 4 | @Injectable() 5 | export class SidebarService { 6 | private openSource = new Subject(); 7 | private open: boolean = false; 8 | 9 | // Observable string streams 10 | open$ = this.openSource.asObservable(); 11 | 12 | toggle() { 13 | this.open = !(this.open); 14 | this.openSource.next(this.open); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user-add.component.html: -------------------------------------------------------------------------------- 1 |

Add user

2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user-add.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { User } from './user'; 3 | import { UserService } from './user.service'; 4 | 5 | @Component({ 6 | moduleId: module.id, 7 | selector: 'lcp-user-add', 8 | templateUrl: 'user-add.component.html' 9 | }) 10 | 11 | export class UserAddComponent {} 12 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user-edit.component.html: -------------------------------------------------------------------------------- 1 |

Edit user

2 | 3 |
4 | 8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Params } from '@angular/router'; 3 | import 'rxjs/add/operator/switchMap'; 4 | 5 | import { User } from './user'; 6 | import { UserService } from './user.service'; 7 | 8 | @Component({ 9 | moduleId: module.id, 10 | selector: 'lcp-user-edit', 11 | templateUrl: 'user-edit.component.html' 12 | }) 13 | 14 | export class UserEditComponent implements OnInit { 15 | user: User; 16 | 17 | constructor( 18 | private route: ActivatedRoute, 19 | private userService: UserService) { 20 | } 21 | 22 | ngOnInit(): void { 23 | this.route.params 24 | .switchMap((params: Params) => this.userService.get(params['id'])) 25 | .subscribe(user => { 26 | this.user = user 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 7 |
8 |
9 | 10 | 13 |
14 |
15 | 16 | 19 |
20 |
21 | 22 | 25 |
26 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { 4 | FormGroup, 5 | FormControl, 6 | Validators, 7 | FormBuilder } from '@angular/forms'; 8 | 9 | import { User } from './user'; 10 | import { UserService } from './user.service'; 11 | 12 | @Component({ 13 | moduleId: module.id, 14 | selector: 'lcp-user-form', 15 | templateUrl: 'user-form.component.html' 16 | }) 17 | 18 | export class UserFormComponent implements OnInit { 19 | @Input() 20 | user: User; 21 | 22 | edit: boolean = false; 23 | submitButtonLabel: string = "Add"; 24 | form: FormGroup; 25 | 26 | private submitted = false; 27 | 28 | constructor( 29 | private fb: FormBuilder, 30 | private router: Router, 31 | private userService: UserService) { 32 | } 33 | 34 | ngOnInit(): void { 35 | if (this.user == null) { 36 | this.user = new User(); 37 | this.submitButtonLabel = "Add"; 38 | this.form = this.fb.group({ 39 | "name": ["", Validators.required], 40 | "email": ["", Validators.required], 41 | "password": ["", Validators.required], 42 | "hint": ["", Validators.required] 43 | }); 44 | } else { 45 | this.edit = true; 46 | this.submitButtonLabel = "Save"; 47 | this.form = this.fb.group({ 48 | "name": [this.user.name, Validators.required], 49 | "email": [this.user.email, Validators.required], 50 | "password": "", 51 | "hint": [this.user.hint, Validators.required] 52 | }); 53 | } 54 | } 55 | 56 | gotoList() { 57 | this.router.navigate(['/users']); 58 | } 59 | 60 | onCancel() { 61 | this.gotoList(); 62 | } 63 | 64 | onSubmit() { 65 | this.bindForm(); 66 | 67 | if (this.edit) { 68 | this.userService.update( 69 | this.user 70 | ).then( 71 | user => { 72 | this.gotoList(); 73 | } 74 | ); 75 | } else { 76 | this.userService.add(this.user).then( 77 | user => { 78 | this.gotoList(); 79 | } 80 | ); 81 | } 82 | 83 | this.submitted = true; 84 | } 85 | 86 | // Bind form to user 87 | bindForm(): void { 88 | this.user.name = this.form.value['name']; 89 | this.user.email = this.form.value['email']; 90 | this.user.hint = this.form.value['hint']; 91 | 92 | let newPassword: string = this.form.value['password']; 93 | newPassword = newPassword.trim(); 94 | 95 | if (newPassword.length > 0) { 96 | this.user.clearPassword = newPassword; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user-list.component.html: -------------------------------------------------------------------------------- 1 |

Users

2 | 3 |
4 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 21 | 26 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 57 | 58 |
17 | # 18 | 19 | 20 | 22 | Name 23 | 24 | 25 | 27 | Email 28 | 29 | 30 | 32 | Hint 33 | 34 | 35 | Actions
{{user.id}}{{user.name}}{{user.email}}{{user.hint}} 46 | 51 | 53 | Edit 54 | 55 |
59 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { User } from './user'; 3 | import { UserService } from './user.service'; 4 | 5 | @Component({ 6 | moduleId: module.id, 7 | selector: 'lcp-user-list', 8 | templateUrl: 'user-list.component.html' 9 | }) 10 | 11 | export class UserListComponent implements OnInit { 12 | users: User[]; 13 | 14 | order: string; 15 | reverse: boolean = false; 16 | 17 | constructor(private userService: UserService) { 18 | this.users = []; 19 | this.order = "id"; 20 | this.reverse = true; 21 | } 22 | 23 | refreshUsers(): void { 24 | this.userService.list().then( 25 | users => { 26 | this.users = users; 27 | } 28 | ); 29 | } 30 | 31 | orderBy(newOrder: string) 32 | { 33 | if (newOrder == this.order) 34 | { 35 | this.reverse = !this.reverse; 36 | } 37 | else 38 | { 39 | this.reverse = false; 40 | this.order = newOrder 41 | } 42 | } 43 | 44 | ngOnInit(): void { 45 | this.refreshUsers(); 46 | } 47 | 48 | onRemove(objId: any): void { 49 | this.userService.delete(objId).then( 50 | user => { 51 | this.refreshUsers(); 52 | } 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { UserListComponent } from './user-list.component'; 5 | import { UserAddComponent } from './user-add.component'; 6 | import { UserEditComponent } from './user-edit.component'; 7 | 8 | const userRoutes: Routes = [ 9 | { path: 'users/:id/edit', component: UserEditComponent }, 10 | { path: 'users/add', component: UserAddComponent }, 11 | { path: 'users', component: UserListComponent } 12 | ]; 13 | 14 | @NgModule({ 15 | imports: [ 16 | RouterModule.forChild(userRoutes) 17 | ], 18 | exports: [ 19 | RouterModule 20 | ] 21 | }) 22 | 23 | export class UserRoutingModule { } 24 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { 5 | FormsModule, 6 | ReactiveFormsModule } from '@angular/forms'; 7 | 8 | import { UserService } from './user.service'; 9 | import { UserRoutingModule } from './user-routing.module'; 10 | import { UserListComponent } from './user-list.component'; 11 | import { UserFormComponent } from './user-form.component'; 12 | import { UserAddComponent } from './user-add.component'; 13 | import { UserEditComponent } from './user-edit.component'; 14 | import { SortModule } from '../shared/pipes/sort.module'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | CommonModule, 19 | RouterModule, 20 | FormsModule, 21 | ReactiveFormsModule, 22 | UserRoutingModule, 23 | SortModule 24 | ], 25 | declarations: [ 26 | UserListComponent, 27 | UserFormComponent, 28 | UserAddComponent, 29 | UserEditComponent 30 | ], 31 | providers: [ 32 | UserService 33 | ] 34 | }) 35 | 36 | export class UserModule { } 37 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | 4 | import 'rxjs/add/operator/toPromise'; 5 | import * as jsSHA from 'jssha'; 6 | 7 | import { User } from './user'; 8 | 9 | import { CrudService } from '../crud/crud.service'; 10 | 11 | declare var Config: any; // this comes from the autogenerated config.js file 12 | 13 | @Injectable() 14 | export class UserService extends CrudService { 15 | constructor (http: Http) { 16 | super(); 17 | this.http = http; 18 | this.baseUrl = Config.frontend.url + '/api/v1/users'; 19 | } 20 | 21 | decode(jsonObj: any): User { 22 | return { 23 | id: jsonObj.id, 24 | uuid: jsonObj.uuid, 25 | name: jsonObj.name, 26 | email: jsonObj.email, 27 | password: jsonObj.password, 28 | clearPassword: null, 29 | hint: jsonObj.hint 30 | } 31 | } 32 | 33 | encode(obj: User): any { 34 | let jsonObj = { 35 | id: obj.id, 36 | uuid: obj.uuid, 37 | name: obj.name, 38 | email: obj.email, 39 | password: obj.password, 40 | hint: obj.hint 41 | }; 42 | 43 | if (obj.clearPassword == null) { 44 | // No password change 45 | return jsonObj; 46 | } 47 | 48 | // Hash password 49 | let jsSHAObject:jsSHA.jsSHA = new jsSHA("SHA-256", "TEXT"); 50 | jsSHAObject.update(obj.clearPassword); 51 | jsonObj['password'] = jsSHAObject.getHash("HEX"); 52 | return jsonObj; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /frontend/manage/app/user/user.ts: -------------------------------------------------------------------------------- 1 | import { CrudItem } from '../crud/crud-item'; 2 | 3 | export class User implements CrudItem { 4 | id: number; 5 | uuid: string; 6 | name: string; 7 | email: string; 8 | password?: string | undefined | null; // Hashed password 9 | clearPassword?: string | undefined | null; 10 | hint?: string | undefined | null; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/manage/assets/sass/main.scss: -------------------------------------------------------------------------------- 1 | @import "../../node_modules/tether/dist/css/tether.min.css"; 2 | @import "../../node_modules/bootstrap/scss/bootstrap.scss"; 3 | 4 | $fa-font-path: "../../node_modules/font-awesome/fonts"; 5 | @import "../../node_modules/font-awesome/scss/font-awesome.scss"; 6 | 7 | 8 | @import "app.scss"; 9 | -------------------------------------------------------------------------------- /frontend/manage/bs-config.js: -------------------------------------------------------------------------------- 1 | var vm = require("vm"); 2 | var fs = require("fs"); 3 | 4 | var configJS = fs.readFileSync("./config.js"); 5 | 6 | global.window = {}; 7 | vm.runInThisContext(configJS); 8 | // eval() has direct access to the local context, no need for global.window = 9 | //eval(configJS); 10 | 11 | var url = global.window.Config.frontend.url; 12 | console.log(url); 13 | var ip = url.replace("http://", "").replace("https://", ""); 14 | var i = ip.indexOf(":"); 15 | if (i > 0) { 16 | ip = ip.substr(0, i); 17 | } 18 | console.log(ip); 19 | 20 | module.exports = { 21 | "baseDir": "./", 22 | "port": 3000, 23 | "files": ["./**/*.{html,htm,css,js}"], 24 | //"logLevel": "debug", 25 | "logPrefix": "Readium BS", 26 | "logConnections": true, 27 | "logFileChanges": true, 28 | "open": "external", 29 | "host": ip 30 | }; -------------------------------------------------------------------------------- /frontend/manage/e2e/app.e2e-spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var protractor_1 = require('protractor'); 3 | describe('QuickStart E2E Tests', function () { 4 | var expectedMsg = 'Hello Angular'; 5 | beforeEach(function () { 6 | protractor_1.browser.get(''); 7 | }); 8 | it('should display: ' + expectedMsg, function () { 9 | expect(protractor_1.element(protractor_1.by.css('h1')).getText()).toEqual(expectedMsg); 10 | }); 11 | }); 12 | //# sourceMappingURL=app.e2e-spec.js.map -------------------------------------------------------------------------------- /frontend/manage/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | describe('QuickStart E2E Tests', function () { 4 | 5 | let expectedMsg = 'Hello Angular'; 6 | 7 | beforeEach(function () { 8 | browser.get(''); 9 | }); 10 | 11 | it('should display: ' + expectedMsg, function () { 12 | expect(element(by.css('h1')).getText()).toEqual(expectedMsg); 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /frontend/manage/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readium/readium-lcp-server/e2c484f571a8013faf13a335c007890150751d79/frontend/manage/favicon.ico -------------------------------------------------------------------------------- /frontend/manage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | testing LCP / LSD server 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | Loading lcp app... 30 | 31 | 32 | -------------------------------------------------------------------------------- /frontend/manage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-angular-app", 3 | "version": "1.0.0", 4 | "description": "Frontend for LCP server", 5 | "scripts": { 6 | "clean": "node ./node_modules/rimraf/bin.js \"dist/**/*.*\"", 7 | "copy-templates": "cpx \"app/**/*.html\" dist/app", 8 | "copy-templates:w": "cpx \"app/**/*.html\" dist/app --watch", 9 | "build-css": "node-sass -r assets/sass -o dist/css", 10 | "build-css:w": "node-sass -w -r assets/sass -o dist/css", 11 | "prestart": "npm run clean && npm run build-css && npm run copy-templates", 12 | "start": "tsc && concurrently \"cpx 'app/**/*.html' dist/app --watch\" \"node-sass -w -r assets/sass -o dist/css\" \"tsc -w\" \"lite-server --config=bs-config.js\" ", 13 | "build": "tsc && cpx 'app/**/*.html' dist/app && node-sass -r assets/sass -o dist/css", 14 | "pree2e": "npm run clean && webdriver-manager update", 15 | "e2e": "tsc && concurrently \"http-server -s\" \"protractor protractor.config.js\" --kill-others --success first", 16 | "pretest": "npm run clean", 17 | "test": "tsc && concurrently \"tsc -w\" \"karma start karma.conf.js\"", 18 | "pretest-once": "npm run clean", 19 | "test-once": "tsc && karma start karma.conf.js --single-run", 20 | "pretsc": "npm run clean", 21 | "tsc": "tsc", 22 | "pretsc:w": "npm run clean", 23 | "tsc:w": "tsc -w", 24 | "lint": "tslint ./app/**/*.ts -t verbose", 25 | "lite": "lite-server --config=bs-config.js" 26 | }, 27 | "keywords": [], 28 | "author": "", 29 | "license": "MIT", 30 | "dependencies": { 31 | "@angular/common": "~2.4.8", 32 | "@angular/compiler": "~2.4.8", 33 | "@angular/core": "~2.4.8", 34 | "@angular/forms": "~2.4.8", 35 | "@angular/http": "~2.4.8", 36 | "@angular/platform-browser": "~2.4.8", 37 | "@angular/platform-browser-dynamic": "~2.4.8", 38 | "@angular/router": "~3.4.8", 39 | "@types/cryptojs": "^3.1.29", 40 | "@types/jquery": "^2.0.40", 41 | "@types/jssha": "0.0.29", 42 | "angular-in-memory-web-api": "~0.3.0", 43 | "bootstrap": "git://github.com/twbs/bootstrap.git#v4-dev", 44 | "core-js": "^2.4.1", 45 | "cpx": "^1.5.0", 46 | "cryptojs": "^2.5.3", 47 | "fast-sha256": "^1.0.0", 48 | "file-saver": "^1.3.3", 49 | "font-awesome": "^4.7.0", 50 | "jquery": "^3.5.0", 51 | "jssha": "^2.2.0", 52 | "moment": "^2.17.1", 53 | "ng2-datetime-picker": "^0.12.8", 54 | "ng2-file-upload": "^1.2.0", 55 | "ng2-slugify": "^0.1.0", 56 | "node-sass": "^4.13.1", 57 | "reflect-metadata": "^0.1.8", 58 | "rxjs": "5.4.2", 59 | "systemjs": "0.19.40", 60 | "tether": "^1.4.0", 61 | "tweetnacl-util": "^0.13.5", 62 | "zone.js": "^0.7.2" 63 | }, 64 | "devDependencies": { 65 | "rimraf": "^2.5.4", 66 | "concurrently": "^3.1.0", 67 | "lite-server": "^2.2.2", 68 | "typescript": "^2.4.0", 69 | "canonical-path": "0.0.2", 70 | "http-server": "^0.9.0", 71 | "tslint": "^3.15.1", 72 | "lodash": "^4.17.21", 73 | "jasmine-core": "~2.4.1", 74 | "karma": "^1.3.0", 75 | "karma-chrome-launcher": "^2.0.0", 76 | "karma-cli": "^1.0.1", 77 | "karma-jasmine": "^1.0.2", 78 | "karma-jasmine-html-reporter": "^0.2.2", 79 | "protractor": "4.0.9", 80 | "webdriver-manager": "10.2.5", 81 | "@types/node": "^6.0.46", 82 | "@types/jasmine": "^2.5.36", 83 | "@types/selenium-webdriver": "^2.53.33" 84 | }, 85 | "repository": {} 86 | } 87 | -------------------------------------------------------------------------------- /frontend/manage/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Lato', sans-serif; 3 | background-color: white; 4 | color: #222; 5 | padding: 0; 6 | margin: 0; 7 | font-size: 14px; 8 | } 9 | 10 | .dropping { 11 | border-color: white; 12 | } 13 | #dropzone { 14 | width: 300px; 15 | } 16 | h1 { 17 | font-size: 16px; 18 | background-color: #1abc9c; 19 | color: white; 20 | padding: 0; 21 | padding-left: 5px; 22 | margin: 0; 23 | } 24 | 25 | h2 { 26 | font-size: 14px; 27 | padding: 0 0 0 5px; 28 | margin: 0; 29 | } 30 | 31 | ul { 32 | margin: 0; 33 | padding: 0; 34 | } 35 | 36 | li { 37 | list-style-type: none; 38 | padding-left: 10px; 39 | cursor: pointer; 40 | } 41 | li.epub { 42 | border-top: 2px solid gray; 43 | border-bottom: 2px solid gray 44 | } 45 | #key { 46 | width: 350px; 47 | font-size: 9px; 48 | } 49 | 50 | .key { 51 | font-size: 9px; 52 | color: silver; 53 | font-weight: bold; 54 | } 55 | .size { 56 | font-size: 9px; 57 | color: red; 58 | font-weight: bold; 59 | } 60 | .sha { 61 | font-size: 9px; 62 | color: green; 63 | font-weight: bold; 64 | } 65 | .download { 66 | color: blue; 67 | font-weight: bold; 68 | } 69 | .licenses { 70 | color: blue; 71 | font-size: 9px; 72 | } 73 | 74 | #license-form { 75 | padding-left: 10px; 76 | } 77 | 78 | .step { 79 | background-color: red; 80 | } -------------------------------------------------------------------------------- /frontend/manage/systemjs.config.extras.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Add barrels and stuff 3 | * Adjust as necessary for your application needs. 4 | */ 5 | // (function (global) { 6 | // System.config({ 7 | // packages: { 8 | // // add packages here 9 | // } 10 | // }); 11 | // })(this); 12 | -------------------------------------------------------------------------------- /frontend/manage/systemjs.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * System configuration for Angular samples 3 | * Adjust as necessary for your application needs. 4 | */ 5 | (function (global) { 6 | System.config({ 7 | paths: { 8 | // paths serve as alias 9 | 'npm:': 'node_modules/' 10 | }, 11 | // map tells the System loader where to look for things 12 | map: { 13 | // our app is within the app folder 14 | app: 'dist/app', 15 | 16 | // angular bundles 17 | '@angular/core': 'npm:@angular/core/bundles/core.umd.js', 18 | '@angular/common': 'npm:@angular/common/bundles/common.umd.js', 19 | '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', 20 | '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', 21 | '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', 22 | '@angular/http': 'npm:@angular/http/bundles/http.umd.js', 23 | '@angular/router': 'npm:@angular/router/bundles/router.umd.js', 24 | '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', 25 | 26 | // other libraries 27 | 'rxjs': 'npm:rxjs', 28 | 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js', 29 | 'ng2-slugify': 'npm:ng2-slugify', 30 | 'jssha': 'npm:jssha/src', 31 | 'ng2-datetime-picker': 'npm:ng2-datetime-picker/dist', 32 | 'moment': 'node_modules/moment', 33 | 'config': '/config.js', 34 | 'file-saver': 'npm:file-saver', 35 | 'ng2-file-upload':'npm:ng2-file-upload/bundles/ng2-file-upload.umd.js' 36 | }, 37 | // packages tells the System loader how to load when no filename and/or no extension 38 | packages: { 39 | app: { 40 | main: './main.js', 41 | defaultExtension: 'js' 42 | }, 43 | rxjs: { 44 | defaultExtension: 'js' 45 | }, 46 | 'jssha': { 47 | main: 'sha.js', 48 | defaultExtension: 'js' 49 | 50 | }, 51 | 'ng2-datetime-picker': { 52 | main: 'ng2-datetime-picker.umd.js', 53 | defaultExtension: 'js' 54 | }, 55 | 'moment': { 56 | main: 'moment.js', 57 | defaultExtension: 'js' 58 | }, 59 | 'file-saver': { 60 | main: './FileSaver.js', 61 | defaultExtension: 'js' 62 | }, 63 | 'ng2-slugify': { 64 | main: './ng2-slugify.js', 65 | defaultExtension: 'js' 66 | } 67 | } 68 | }); 69 | })(this); 70 | -------------------------------------------------------------------------------- /frontend/manage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "lib": [ "es2015", "dom" ], 10 | "noImplicitAny": true, 11 | "suppressImplicitAnyIndexErrors": true, 12 | "ignoreDeprecations": "5.0", 13 | "outDir": "dist/app", 14 | "typeRoots": [ 15 | "node_modules/@types", 16 | "./typings" 17 | ] 18 | }, 19 | "include": [ 20 | "app/**/*" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "**/*.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /frontend/manage/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "curly": true, 9 | "eofline": true, 10 | "forin": true, 11 | "indent": [ 12 | true, 13 | "spaces" 14 | ], 15 | "label-position": true, 16 | "label-undefined": true, 17 | "max-line-length": [ 18 | true, 19 | 140 20 | ], 21 | "member-access": false, 22 | "member-ordering": [ 23 | true, 24 | "static-before-instance", 25 | "variables-before-functions" 26 | ], 27 | "no-arg": true, 28 | "no-bitwise": true, 29 | "no-console": [ 30 | true, 31 | "debug", 32 | "info", 33 | "time", 34 | "timeEnd", 35 | "trace" 36 | ], 37 | "no-construct": true, 38 | "no-debugger": true, 39 | "no-duplicate-key": true, 40 | "no-duplicate-variable": true, 41 | "no-empty": false, 42 | "no-eval": true, 43 | "no-inferrable-types": true, 44 | "no-shadowed-variable": true, 45 | "no-string-literal": false, 46 | "no-switch-case-fall-through": true, 47 | "no-trailing-whitespace": true, 48 | "no-unused-expression": true, 49 | "no-unused-variable": true, 50 | "no-unreachable": true, 51 | "no-use-before-declare": true, 52 | "no-var-keyword": true, 53 | "object-literal-sort-keys": false, 54 | "one-line": [ 55 | true, 56 | "check-open-brace", 57 | "check-catch", 58 | "check-else", 59 | "check-whitespace" 60 | ], 61 | "quotemark": [ 62 | true, 63 | "single" 64 | ], 65 | "radix": true, 66 | "semicolon": [ 67 | "always" 68 | ], 69 | "triple-equals": [ 70 | true, 71 | "allow-null-check" 72 | ], 73 | "typedef-whitespace": [ 74 | true, 75 | { 76 | "call-signature": "nospace", 77 | "index-signature": "nospace", 78 | "parameter": "nospace", 79 | "property-declaration": "nospace", 80 | "variable-declaration": "nospace" 81 | } 82 | ], 83 | "variable-name": false, 84 | "whitespace": [ 85 | true, 86 | "check-branch", 87 | "check-decl", 88 | "check-operator", 89 | "check-separator", 90 | "check-type" 91 | ] 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /frontend/manage/typings/file-saver/index.d.ts: -------------------------------------------------------------------------------- 1 | interface FileSaver { 2 | ( 3 | /** 4 | * @summary Data. 5 | * @type {Blob} 6 | */ 7 | data: Blob, 8 | 9 | /** 10 | * @summary File name. 11 | * @type {DOMString} 12 | */ 13 | filename: string, 14 | 15 | /** 16 | * @summary Disable Unicode text encoding hints or not. 17 | * @type {boolean} 18 | */ 19 | disableAutoBOM?: boolean 20 | ): void 21 | } 22 | 23 | declare var saveAs: FileSaver; 24 | 25 | declare module "file-saver" { 26 | export = saveAs 27 | } 28 | -------------------------------------------------------------------------------- /frontend/server/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package frontend 27 | 28 | import ( 29 | "testing" 30 | ) 31 | 32 | func TestSetup(t *testing.T) { 33 | } 34 | -------------------------------------------------------------------------------- /frontend/webrepository/webrepository.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Readium Foundation 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 webrepository 6 | 7 | import ( 8 | "errors" 9 | "io/ioutil" 10 | "os" 11 | "path" 12 | 13 | "github.com/readium/readium-lcp-server/config" 14 | ) 15 | 16 | // ErrNotFound error trown when repository is not found 17 | var ErrNotFound = errors.New("Repository not found") 18 | 19 | // WebRepository interface for repository db interaction 20 | type WebRepository interface { 21 | GetMasterFile(name string) (RepositoryFile, error) 22 | GetMasterFiles() func() (RepositoryFile, error) 23 | } 24 | 25 | // RepositoryFile struct defines a file stored in a repository 26 | type RepositoryFile struct { 27 | Name string `json:"name"` 28 | Path string 29 | } 30 | 31 | // RepositoryManager contains all repository definitions 32 | type RepositoryManager struct { 33 | MasterRepositoryPath string 34 | EncryptedRepositoryPath string 35 | } 36 | 37 | // GetMasterFile returns a specific repository file 38 | func (repManager RepositoryManager) GetMasterFile(name string) (RepositoryFile, error) { 39 | var filePath = path.Join(repManager.MasterRepositoryPath, name) 40 | 41 | if _, err := os.Stat(filePath); err == nil { 42 | // File exists 43 | var repFile RepositoryFile 44 | repFile.Name = name 45 | repFile.Path = filePath 46 | return repFile, err 47 | } 48 | 49 | return RepositoryFile{}, ErrNotFound 50 | } 51 | 52 | // GetMasterFiles returns all filenames from the master repository 53 | func (repManager RepositoryManager) GetMasterFiles() func() (RepositoryFile, error) { 54 | files, err := ioutil.ReadDir(repManager.MasterRepositoryPath) 55 | var fileIndex int 56 | 57 | if err != nil { 58 | return func() (RepositoryFile, error) { return RepositoryFile{}, err } 59 | } 60 | 61 | return func() (RepositoryFile, error) { 62 | var repFile RepositoryFile 63 | 64 | for fileIndex < len(files) { 65 | file := files[fileIndex] 66 | repFile.Name = file.Name() 67 | fileIndex++ 68 | return repFile, err 69 | } 70 | 71 | return repFile, ErrNotFound 72 | } 73 | } 74 | 75 | // Init returns a WebPublication interface (db interaction) 76 | func Init(config config.FrontendServerInfo) (i WebRepository, err error) { 77 | i = RepositoryManager{config.MasterRepository, config.EncryptedRepository} 78 | return 79 | } 80 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/readium/readium-lcp-server 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/Machiel/slugify v1.0.1 7 | github.com/abbot/go-http-auth v0.4.0 8 | github.com/aws/aws-sdk-go v1.52.5 9 | github.com/claudiu/gocron v0.0.0-20151103142354-980c96bf412b 10 | github.com/go-sql-driver/mysql v1.8.1 11 | github.com/gorilla/mux v1.8.0 12 | github.com/jeffbmartinez/delay v0.0.0-20150608194421-e1b689d78b33 13 | github.com/jtacoma/uritemplates v1.0.0 14 | github.com/lib/pq v1.10.9 15 | github.com/mattn/go-sqlite3 v1.14.22 16 | github.com/microsoft/go-mssqldb v1.7.1 17 | github.com/rickb777/date v1.20.6 18 | github.com/rs/cors v1.11.0 19 | github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b 20 | github.com/slack-go/slack v0.12.5 21 | github.com/urfave/negroni v1.0.0 22 | golang.org/x/net v0.25.0 23 | golang.org/x/text v0.21.0 24 | gopkg.in/yaml.v2 v2.4.0 25 | ) 26 | 27 | require ( 28 | filippo.io/edwards25519 v1.1.0 // indirect 29 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect 30 | github.com/golang-sql/sqlexp v0.1.0 // indirect 31 | github.com/gorilla/websocket v1.4.2 // indirect 32 | github.com/jmespath/go-jmespath v0.4.0 // indirect 33 | github.com/rickb777/plural v1.4.1 // indirect 34 | golang.org/x/crypto v0.31.0 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /index/index_test.go: -------------------------------------------------------------------------------- 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 index 6 | 7 | import ( 8 | "database/sql" 9 | "testing" 10 | 11 | _ "github.com/mattn/go-sqlite3" 12 | 13 | "github.com/readium/readium-lcp-server/config" 14 | ) 15 | 16 | func TestCRUD(t *testing.T) { 17 | 18 | config.Config.LcpServer.Database = "sqlite3://:memory:" 19 | driver, cnxn := config.GetDatabase(config.Config.LcpServer.Database) 20 | db, err := sql.Open(driver, cnxn) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | err = db.Ping() 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | idx, err := Open(db) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | c := Content{ID: "test20", 36 | EncryptionKey: []byte("1234"), 37 | Location: "test.epub", 38 | Length: 1000, 39 | Sha256: "xxxx", 40 | Type: "epub"} 41 | 42 | err = idx.Add(c) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | cbis, err := idx.Get(c.ID) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | if c.ID != cbis.ID { 51 | t.Fatal("Failed to Get back the record") 52 | } 53 | 54 | c.Location = "test.epub" 55 | err = idx.Update(c) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | c2 := Content{ID: "test21", 61 | EncryptionKey: []byte("1234"), 62 | Location: "test2.epub", 63 | Length: 2000, 64 | Sha256: "xxxx", 65 | Type: "epub"} 66 | 67 | err = idx.Add(c2) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | 72 | fn := idx.List() 73 | contents := make([]Content, 0) 74 | 75 | for it, err := fn(); err == nil; it, err = fn() { 76 | contents = append(contents, it) 77 | } 78 | if len(contents) != 2 { 79 | t.Fatal("Failed to List two rows") 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /lcpencrypt/get_content_info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 main 6 | 7 | import ( 8 | b64 "encoding/base64" 9 | "encoding/json" 10 | "errors" 11 | "fmt" 12 | "net/http" 13 | "net/url" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | type ContentInfo struct { 19 | ID string `json:"id"` 20 | EncryptionKey []byte `json:"key,omitempty"` 21 | Location string `json:"location"` 22 | Length int64 `json:"length"` 23 | Sha256 string `json:"sha256"` 24 | Type string `json:"type"` 25 | } 26 | 27 | // getContentKey gets content information from the License Server 28 | // for a given content id, 29 | // and returns the associated content key. 30 | func getContentKey(contentKey *string, contentID, lcpsv string, v2 bool, username, password string) error { 31 | 32 | // An empty notify URL is not an error, simply a silent encryption 33 | if lcpsv == "" { 34 | return nil 35 | } 36 | 37 | if !strings.HasPrefix(lcpsv, "http://") && !strings.HasPrefix(lcpsv, "https://") { 38 | lcpsv = "http://" + lcpsv 39 | } 40 | var getInfoURL string 41 | var err error 42 | if v2 { 43 | getInfoURL, err = url.JoinPath(lcpsv, "publications", contentID, "info") 44 | } else { 45 | getInfoURL, err = url.JoinPath(lcpsv, "contents", contentID, "info") 46 | } 47 | if err != nil { 48 | return err 49 | } 50 | 51 | // look for the username and password in the url 52 | err = getUsernamePassword(&getInfoURL, &username, &password) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | req, err := http.NewRequest("GET", getInfoURL, nil) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | req.SetBasicAuth(username, password) 63 | client := &http.Client{ 64 | Timeout: 15 * time.Second, 65 | } 66 | resp, err := client.Do(req) 67 | if err != nil { 68 | return err 69 | } 70 | defer resp.Body.Close() 71 | 72 | // if the content is found, the content key is updated 73 | if resp.StatusCode == http.StatusOK { 74 | contentInfo := ContentInfo{} 75 | dec := json.NewDecoder(resp.Body) 76 | err = dec.Decode(&contentInfo) 77 | if err != nil { 78 | return errors.New("unable to decode content information") 79 | } 80 | 81 | *contentKey = b64.StdEncoding.EncodeToString(contentInfo.EncryptionKey) 82 | fmt.Println("Existing encryption key retrieved") 83 | } 84 | return nil 85 | } 86 | 87 | // Look for the username and password in the url 88 | func getUsernamePassword(notifyURL, username, password *string) error { 89 | u, err := url.Parse(*notifyURL) 90 | if err != nil { 91 | return err 92 | } 93 | un := u.User.Username() 94 | pw, pwfound := u.User.Password() 95 | if un != "" && pwfound { 96 | *username = un 97 | *password = pw 98 | u.User = nil 99 | *notifyURL = u.String() // notifyURL is updated 100 | } 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /lcpserver/server/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package lcpserver 27 | 28 | import ( 29 | "testing" 30 | ) 31 | 32 | func TestSetup(t *testing.T) { 33 | } 34 | -------------------------------------------------------------------------------- /license/license_test.go: -------------------------------------------------------------------------------- 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 license 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/readium/readium-lcp-server/config" 11 | ) 12 | 13 | func TestLicense(t *testing.T) { 14 | config.Config.Profile = "1.0" 15 | 16 | l := License{} 17 | contentID := "1234-1234-1234-1234" 18 | Initialize(contentID, &l) 19 | if l.ID == "" { 20 | t.Error("Should have an id") 21 | } 22 | 23 | err := SetLicenseProfile(&l) 24 | if err != nil { 25 | t.Error(err.Error()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /license/user_key.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 European Digital Reading Lab. All rights reserved. 2 | // Licensed to the Readium Foundation under one or more contributor license agreements. 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 5 | 6 | package license 7 | 8 | import ( 9 | "log" 10 | 11 | "github.com/readium/readium-lcp-server/config" 12 | ) 13 | 14 | var LCP_PRODUCTION_LIB = false 15 | 16 | // GenerateUserKey function prepares the user key 17 | func GenerateUserKey(key UserKey) []byte { 18 | if config.Config.Profile != "basic" { 19 | log.Println("Incompatible LCP profile") 20 | return nil 21 | } 22 | return key.Value 23 | } 24 | -------------------------------------------------------------------------------- /license_statuses/license_status.go: -------------------------------------------------------------------------------- 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 licensestatuses 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/readium/readium-lcp-server/transactions" 11 | ) 12 | 13 | // Updated represents license and status document timestamps 14 | type Updated struct { 15 | License *time.Time `json:"license,omitempty"` 16 | Status *time.Time `json:"status,omitempty"` 17 | } 18 | 19 | // Link represents a link object 20 | type Link struct { 21 | Rel string `json:"rel"` 22 | Href string `json:"href"` 23 | Type string `json:"type,omitempty"` 24 | Title string `json:"title,omitempty"` 25 | Profile string `json:"profile,omitempty"` 26 | Templated bool `json:"templated,omitempty"` 27 | } 28 | 29 | // PotentialRights represents the maximal extension time of a loan 30 | type PotentialRights struct { 31 | End *time.Time `json:"end,omitempty"` 32 | } 33 | 34 | // LicenseStatus represents a license status 35 | type LicenseStatus struct { 36 | ID int `json:"-"` 37 | LicenseRef string `json:"id"` 38 | Status string `json:"status"` 39 | Updated *Updated `json:"updated,omitempty"` 40 | Message string `json:"message"` 41 | Links []Link `json:"links,omitempty"` 42 | DeviceCount *int `json:"device_count,omitempty"` 43 | PotentialRights *PotentialRights `json:"potential_rights,omitempty"` 44 | Events []transactions.Event `json:"events,omitempty"` 45 | CurrentEndLicense *time.Time `json:"-"` 46 | } 47 | -------------------------------------------------------------------------------- /license_statuses/license_statuses_test.go: -------------------------------------------------------------------------------- 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 licensestatuses 6 | 7 | import ( 8 | "database/sql" 9 | "testing" 10 | "time" 11 | 12 | _ "github.com/mattn/go-sqlite3" 13 | 14 | "github.com/readium/readium-lcp-server/config" 15 | ) 16 | 17 | func TestCRUD(t *testing.T) { 18 | 19 | config.Config.LsdServer.Database = "sqlite3://:memory:" 20 | driver, cnxn := config.GetDatabase(config.Config.LsdServer.Database) 21 | db, err := sql.Open(driver, cnxn) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | lst, err := Open(db) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | timestamp := time.Now().UTC().Truncate(time.Second) 32 | 33 | // add 34 | count := 2 35 | ls := LicenseStatus{PotentialRights: &PotentialRights{End: ×tamp}, LicenseRef: "licenseref", Status: "active", Updated: &Updated{License: ×tamp, Status: ×tamp}, DeviceCount: &count} 36 | err = lst.Add(ls) 37 | if err != nil { 38 | t.Error(err) 39 | } 40 | 41 | // list with no device limit, 10 max, offset 0 42 | fn := lst.List(0, 10, 0) 43 | if fn == nil { 44 | t.Errorf("Failed getting a non null list function") 45 | } 46 | statusList := make([]LicenseStatus, 0) 47 | var it LicenseStatus 48 | for it, err = fn(); err == nil; it, err = fn() { 49 | statusList = append(statusList, it) 50 | } 51 | if err != ErrNotFound { 52 | t.Error(err) 53 | } 54 | if len(statusList) != 1 { 55 | t.Errorf("Failed getting a list with one item, got %d instead", len(statusList)) 56 | } 57 | 58 | // get by id 59 | statusID := statusList[0].ID 60 | _, err = lst.GetByID(statusID) 61 | if err != nil { 62 | t.Error(err) 63 | } 64 | 65 | // get by license id 66 | ls2, err := lst.GetByLicenseID("licenseref") 67 | if err != nil { 68 | t.Error(err) 69 | } 70 | if ls2.ID != statusID { 71 | t.Errorf("Failed getting a license status by license id") 72 | } 73 | 74 | // update 75 | ls2.Status = "revoked" 76 | err = lst.Update(*ls2) 77 | if err != nil { 78 | t.Error(err) 79 | } 80 | 81 | ls3, err := lst.GetByID(ls2.ID) 82 | if err != nil { 83 | t.Error(err) 84 | } 85 | if ls3.Status != "revoked" { 86 | t.Errorf("Failed getting the proper stats, got %s instead", ls3.Status) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /logging/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 European Digital Reading Lab. All rights reserved. 2 | // Licensed to the Readium Foundation under one or more contributor license agreements. 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 5 | 6 | package logging 7 | 8 | import ( 9 | "errors" 10 | "log" 11 | "os" 12 | "time" 13 | 14 | "github.com/readium/readium-lcp-server/config" 15 | "github.com/slack-go/slack" 16 | ) 17 | 18 | var ( 19 | LogFile *log.Logger 20 | SlackApi *slack.Client 21 | SlackChannelID string 22 | ) 23 | 24 | // Init inits the log file and opens it 25 | func Init(logging config.Logging) error { 26 | 27 | //logPath string, cm bool 28 | if logging.Directory != "" { 29 | log.Println("Open log file as " + logging.Directory) 30 | file, err := os.OpenFile(logging.Directory, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | LogFile = log.New(file, "", log.LUTC) 36 | } 37 | if logging.SlackToken != "" && logging.SlackChannelID != "" { 38 | log.Println("Init Slack connection") 39 | SlackApi = slack.New(logging.SlackToken) 40 | if SlackApi == nil { 41 | return errors.New("error creating a Slack connector") 42 | } 43 | SlackChannelID = logging.SlackChannelID 44 | } 45 | return nil 46 | } 47 | 48 | // Print writes a message to the log file / Slack 49 | func Print(message string) { 50 | // log on stdout 51 | log.Print(message) 52 | 53 | // log on a file 54 | if LogFile != nil { 55 | currentTime := time.Now().UTC().Format(time.RFC3339) 56 | LogFile.Println(currentTime + "\t" + message) 57 | } 58 | 59 | // log on Slack 60 | if SlackApi != nil { 61 | _, _, err := SlackApi.PostMessage(SlackChannelID, slack.MsgOptionText(message, false)) 62 | if err != nil { 63 | log.Printf("Error sending Slack msg, %v", err) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lsdserver/api/freshlicense_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 | "log" 9 | "testing" 10 | 11 | "github.com/readium/readium-lcp-server/config" 12 | ) 13 | 14 | // enter here an existing license id 15 | var LicenseID string = "812bbfe8-9a57-4b14-b8f3-4e0fc6e841c0" 16 | 17 | func TestGetUserData(t *testing.T) { 18 | 19 | // enter here a valid URL 20 | config.Config.LsdServer.UserDataUrl = "http://xx.xx.xx.xx:pppp/aaaaa/{license_id}/aaaa" 21 | // enter here valid credentials 22 | config.Config.CMSAccessAuth.Username = "xxxxx" 23 | config.Config.CMSAccessAuth.Password = "xxxxx" 24 | 25 | log.Println("username ", config.Config.CMSAccessAuth.Username) 26 | 27 | userData, err := getUserData(LicenseID) 28 | if err != nil { 29 | t.Error(err.Error()) 30 | t.FailNow() 31 | } 32 | 33 | if userData.Name == "" { 34 | t.Error("Unexpected user name") 35 | } 36 | } 37 | 38 | func TestInitPartialLicense(t *testing.T) { 39 | 40 | userData := UserData{ 41 | ID: "123-123-123", 42 | Name: "John Doe", 43 | Email: "jdoe@example.com", 44 | Hint: "Good hint", 45 | PassphraseHash: "faeb00ca518bea7cb11a7ef31f63183b489b1b6eadb792bec64a03b3f6ff80a8", 46 | } 47 | 48 | plic, err := initPartialLicense(LicenseID, userData) 49 | 50 | if err != nil { 51 | t.Error(err.Error()) 52 | t.FailNow() 53 | } 54 | if plic.User.ID != userData.ID { 55 | t.Error("Unexpected user ID") 56 | } 57 | 58 | if plic.Encryption.UserKey.Algorithm != Sha256_URL { 59 | t.Error("Unexpected UserKey algorithm") 60 | } 61 | } 62 | 63 | func TestFetchLicense(t *testing.T) { 64 | 65 | // enter here a valid URL 66 | config.Config.LcpServer.PublicBaseUrl = "http://xx.xx.xx.xx:pppp" 67 | // enter here valid credentials 68 | config.Config.LcpUpdateAuth.Username = "xxxx" 69 | config.Config.LcpUpdateAuth.Password = "xxxx" 70 | 71 | userData := UserData{ 72 | ID: "123-123-123", 73 | Name: "John Doe", 74 | Email: "jdoe@example.com", 75 | Hint: "Good hint", 76 | PassphraseHash: "faeb00ca518bea7cb11sdf434b6183b489b1b6eadb792bec64a03b3f6ff80a8", 77 | } 78 | 79 | plic, _ := initPartialLicense(LicenseID, userData) 80 | 81 | _, err := fetchLicense(plic) 82 | if err != nil { 83 | t.Error(err.Error()) 84 | t.FailNow() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lsdserver/server/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package lsdserver 27 | 28 | import ( 29 | "testing" 30 | ) 31 | 32 | func TestSetup(t *testing.T) { 33 | } 34 | -------------------------------------------------------------------------------- /pack/samples/basic.webpub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readium/readium-lcp-server/e2c484f571a8013faf13a335c007890150751d79/pack/samples/basic.webpub -------------------------------------------------------------------------------- /pack/samples/w3cman1.json: -------------------------------------------------------------------------------- 1 | { 2 | "conformsTo": "https://www.w3/org/TR/audiobooks/", 3 | "id": "id1", 4 | "name": "audiotest", 5 | "publisher": "Stanford", 6 | "author": ["Alpha", {"name":"Beta","identifier":"b"}, {"name":{"fr":"Gamma"},"sortAs":"g"}, {"name":{"fr":"Zeta","en":"Taze"}}], 7 | "readBy": "Music", 8 | "inLanguage": ["fr","en"], 9 | "datePublished": "2020-03-23T12:50:20Z", 10 | "dateModified": "2020-03-23T16:58:27.372Z", 11 | "readingProgression": "ltr", 12 | "duration": "PT150S", 13 | "readingOrder": [ 14 | { 15 | "url": "audio/gtr-jazz.mp3", 16 | "encodingFormat": "audio/mpeg", 17 | "name": "Track 1", 18 | "duration": "PT10S" 19 | }, 20 | { 21 | "url": "audio/Latin.mp3", 22 | "name": "Track 2", 23 | "duration": "PT12S", 24 | "description":"Latin track", 25 | "alternate": [ 26 | { 27 | "url": "audio/Latin.mp3" 28 | } 29 | ] 30 | } 31 | ], 32 | "resources": [ 33 | { 34 | "url": "cover/audiobook-cover.webp", 35 | "encodingFormat": "image/webp", 36 | "name": "Cover" 37 | }, 38 | { 39 | "url": "index.html", 40 | "encodingFormat": "text/html", 41 | "name": "Table of Contents", 42 | "description": "An XHTML document with the ToC" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /pack/w3cpackage_test.go: -------------------------------------------------------------------------------- 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 pack 6 | 7 | import ( 8 | "encoding/json" 9 | "os" 10 | "testing" 11 | "time" 12 | 13 | "github.com/readium/readium-lcp-server/rwpm" 14 | ) 15 | 16 | // TestMapW3CPublication tests the mapping of a W3C manifest to a Readium manifest 17 | func TestMapW3CPublication(t *testing.T) { 18 | 19 | file, err := os.Open("./samples/w3cman1.json") 20 | if err != nil { 21 | t.Fatalf("Could not find the sample file, %s", err) 22 | } 23 | defer file.Close() 24 | 25 | var w3cManifest rwpm.W3CPublication 26 | decoder := json.NewDecoder(file) 27 | err = decoder.Decode(&w3cManifest) 28 | if err != nil { 29 | t.Fatalf("Could not unmarshal sample file, %s", err) 30 | } 31 | 32 | rman := generateRWPManifest(w3cManifest) 33 | 34 | // metadata 35 | meta := rman.Metadata 36 | 37 | if meta.Identifier != "id1" { 38 | t.Fatalf("W3C Identifer badly mapped") 39 | } 40 | if meta.Title.Text() != "audiotest" { 41 | t.Fatalf("W3C Name badly mapped") 42 | } 43 | if meta.Publisher.Name() != "Stanford" { 44 | t.Fatalf("W3C Publisher badly mapped") 45 | } 46 | if meta.Author.Name() != "" { 47 | t.Fatalf("W3C Author badly mapped 1") 48 | } 49 | 50 | i := 0 51 | for _, a := range meta.Author { 52 | if a.Name.Text() == "Alpha" || a.Name.Text() == "Beta" || a.Name.Text() == "Gamma" { 53 | i++ 54 | } 55 | } 56 | if i != 2 { 57 | t.Fatalf("W3C Author badly mapped, expected 2 got %d", i) 58 | } 59 | if meta.Language[0] != "fr" || meta.Language[1] != "en" { 60 | t.Fatalf("W3C InLanguage badly mapped") 61 | } 62 | if *meta.Published != rwpm.Date(time.Date(2020, 03, 23, 12, 50, 20, 0, time.UTC)) { 63 | t.Fatalf("W3C DatePublished badly mapped") 64 | } 65 | mod := time.Date(2020, 03, 23, 16, 58, 27, 372000000, time.UTC) 66 | if *meta.Modified != mod { 67 | t.Fatalf("W3C DateModified badly mapped") 68 | } 69 | if meta.Duration != 150 { 70 | t.Fatalf("W3C Duration badly mapped") 71 | } 72 | 73 | // Linked resources 74 | item0 := rman.ReadingOrder[0] 75 | if item0.Href != "audio/gtr-jazz.mp3" { 76 | t.Fatalf("W3C URL badly mapped") 77 | } 78 | if item0.Type != "audio/mpeg" { 79 | t.Fatalf("W3C EncodingFormat badly mapped") 80 | } 81 | if item0.Title != "Track 1" { 82 | t.Fatalf("W3C Name badly mapped") 83 | } 84 | if item0.Duration != 10 { 85 | t.Fatalf("W3C Duration badly mapped") 86 | } 87 | 88 | item1 := rman.ReadingOrder[1] 89 | if item1.Type != "audio/mpeg" { 90 | t.Fatalf("W3C EncodingFormat badly mapped if missing") 91 | } 92 | if item1.Alternate[0].Href != "audio/Latin.mp3" { 93 | t.Fatalf("W3C Name badly mapped in Alternate") 94 | } 95 | if item1.Alternate[0].Type != "audio/mpeg" { 96 | t.Fatalf("W3C EncodingFormat badly mapped in Alternate") 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /problem/problem.go: -------------------------------------------------------------------------------- 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 problem 6 | 7 | // rfc 7807 : https://tools.ietf.org/html/rfc7807 8 | // problem.Type should be an URI 9 | // for example http://readium.org/readium/[lcpserver|lsdserver]/ 10 | // for standard http error messages use "about:blank" status in json equals http status 11 | import ( 12 | "encoding/json" 13 | "fmt" 14 | "log" 15 | "net/http" 16 | "runtime/debug" 17 | "strings" 18 | ) 19 | 20 | const ( 21 | ContentType_PROBLEM_JSON = "application/problem+json" 22 | ) 23 | 24 | type Problem struct { 25 | Type string `json:"type,omitempty"` 26 | Title string `json:"title,omitempty"` 27 | //optional 28 | Status int `json:"status,omitempty"` //if present = http response code 29 | Detail string `json:"detail,omitempty"` 30 | Instance string `json:"instance,omitempty"` 31 | } 32 | 33 | // Problem types 34 | const ERROR_BASE_URL = "http://readium.org/license-status-document/error/" 35 | const LICENSE_NOT_FOUND = ERROR_BASE_URL + "notfound" 36 | const SERVER_INTERNAL_ERROR = ERROR_BASE_URL + "server" 37 | const REGISTRATION_BAD_REQUEST = ERROR_BASE_URL + "registration" 38 | const RETURN_BAD_REQUEST = ERROR_BASE_URL + "return" 39 | const RETURN_EXPIRED = ERROR_BASE_URL + "return/expired" 40 | const RETURN_ALREADY = ERROR_BASE_URL + "return/already" 41 | const RENEW_BAD_REQUEST = ERROR_BASE_URL + "renew" 42 | const RENEW_REJECT = ERROR_BASE_URL + "renew/date" 43 | const CANCEL_BAD_REQUEST = ERROR_BASE_URL + "cancel" 44 | const FILTER_BAD_REQUEST = ERROR_BASE_URL + "filter" 45 | 46 | func Error(w http.ResponseWriter, r *http.Request, problem Problem, status int) { 47 | 48 | w.Header().Set("Content-Type", ContentType_PROBLEM_JSON) 49 | w.Header().Set("X-Content-Type-Options", "nosniff") 50 | 51 | // must come *after* w.Header().Add()/Set(), but before w.Write() 52 | w.WriteHeader(status) 53 | 54 | problem.Status = status 55 | 56 | if problem.Type == "" && status == http.StatusInternalServerError { 57 | problem.Type = SERVER_INTERNAL_ERROR 58 | } 59 | 60 | if problem.Title == "" { // Title (required) matches http status by default 61 | problem.Title = http.StatusText(status) 62 | } 63 | 64 | jsonError, e := json.Marshal(problem) 65 | if e != nil { 66 | http.Error(w, "{}", problem.Status) 67 | } 68 | fmt.Fprintln(w, string(jsonError)) 69 | 70 | // debug only 71 | //PrintStack() 72 | } 73 | 74 | func PrintStack() { 75 | log.Print("####################") 76 | 77 | //debug.PrintStack() 78 | 79 | b := debug.Stack() 80 | s := string(b[:]) 81 | l := strings.Index(s, "ServeHTTP") 82 | if l > 0 { 83 | ss := s[0:l] 84 | log.Print(ss + " [...]") 85 | } else { 86 | log.Print(s) 87 | } 88 | 89 | log.Print("####################") 90 | } 91 | 92 | // NotFoundHandler handles 404 API errors 93 | func NotFoundHandler(w http.ResponseWriter, r *http.Request) { 94 | Error(w, r, Problem{Detail: r.URL.String()}, http.StatusNotFound) 95 | } 96 | -------------------------------------------------------------------------------- /sign/canon.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 European Digital Reading Lab. All rights reserved. 2 | // Licensed to the Readium Foundation under one or more contributor license agreements. 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 5 | 6 | package sign 7 | 8 | import ( 9 | "bytes" 10 | "encoding/json" 11 | "io" 12 | "strings" 13 | ) 14 | 15 | func Canon(in interface{}) ([]byte, error) { 16 | // the easiest way to canonicalize is to marshal it and reify it as a map 17 | // which will sort stuff correctly 18 | b, err1 := json.Marshal(in) 19 | if err1 != nil { 20 | return b, err1 21 | } 22 | 23 | var jsonObj interface{} // map[string]interface{} ==> auto sorting 24 | 25 | dec := json.NewDecoder(strings.NewReader(string(b))) 26 | dec.UseNumber() 27 | for { 28 | if err2 := dec.Decode(&jsonObj); err2 == io.EOF { 29 | break 30 | } else if err2 != nil { 31 | return nil, err2 32 | } 33 | } 34 | var buf bytes.Buffer 35 | enc := json.NewEncoder(&buf) 36 | // do not escape characters 37 | enc.SetEscapeHTML(false) 38 | err := enc.Encode(jsonObj) 39 | if err != nil { 40 | return nil, err 41 | } 42 | // remove the trailing newline, added by encode 43 | return bytes.TrimRight(buf.Bytes(), "\n"), nil 44 | 45 | } 46 | -------------------------------------------------------------------------------- /sign/canon_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package sign 27 | 28 | import ( 29 | "bytes" 30 | "testing" 31 | ) 32 | 33 | func TestCanonSimple(t *testing.T) { 34 | input := make(map[string]interface{}) 35 | input["test"] = 1 36 | input["abc"] = "test" 37 | out, err := Canon(input) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | expected := `{"abc":"test","test":1}` 42 | if !bytes.Equal(out, []byte(expected)) { 43 | t.Errorf("Expected %s, got %s", expected, out) 44 | } 45 | } 46 | 47 | type simpleStruct struct { 48 | B string 49 | A string 50 | } 51 | 52 | func TestCanonStruct(t *testing.T) { 53 | expected := `{"A":"1","B":"2"}` 54 | out, err := Canon(simpleStruct{"2", "1"}) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | if !bytes.Equal(out, []byte(expected)) { 60 | t.Errorf("Expected %s, got %s", expected, out) 61 | } 62 | } 63 | 64 | type nestedStruct struct { 65 | Test string 66 | Inner simpleStruct 67 | } 68 | 69 | func TestCanonInnerStruct(t *testing.T) { 70 | expected := `{"Inner":{"A":"1","B":"2"},"Test":"Blah"}` 71 | out, err := Canon(nestedStruct{"Blah", simpleStruct{"2", "1"}}) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | if !bytes.Equal(out, []byte(expected)) { 77 | t.Errorf("Expected %s, got %s", expected, out) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /sign/cert/sample_ecdsa.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICrzCCAhGgAwIBAgIJALjD7OoQGALZMAkGByqGSM49BAEwRTELMAkGA1UEBhMC 3 | QVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp 4 | dHMgUHR5IEx0ZDAeFw0xNjAzMDMyMTI0NTBaFw0xNjA0MDIyMTI0NTBaMEUxCzAJ 5 | BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l 6 | dCBXaWRnaXRzIFB0eSBMdGQwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABADEgi0h 7 | RyKVT+O2AECjLyCDN/K01/O6B8kMkFduX4JCYdYC04bZmdjgLspgLenYi8ih6N20 8 | 0dCgVrKBmFqesPnGcwH3LmyQ4T8nULE8Sb1DI51+xpBcYPOecStuBHS+/bIbYvQC 9 | zpQajUcVxE+LFPfFU3jHSxHjR+7MCDGZe4E4KQbEeKOBpzCBpDAdBgNVHQ4EFgQU 10 | Q03I5mxR9FhDsJ2fhcCq25k5P7wwdQYDVR0jBG4wbIAUQ03I5mxR9FhDsJ2fhcCq 11 | 25k5P7yhSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEw 12 | HwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQC4w+zqEBgC2TAMBgNV 13 | HRMEBTADAQH/MAkGByqGSM49BAEDgYwAMIGIAkIAjh4ctfeKdkp5Df+zWiQsAQB3 14 | Hay1lixhPxUb8cY/qPkfESzSGOUJ9oVOVIOH2CmK60GY6w6xbFDMjhxJdTh+UAwC 15 | QgFkb8pis4+CyByZkw4jeF2atyxJL6zLiUwc+A2m7cl9cBwErCp7fZO3ajbq7sor 16 | /cIrD10iWLolPKKxU+095wSHSg== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /sign/cert/sample_ecdsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIw== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MIHcAgEBBEIBqGtg3Nnp9eR8f7RbrtUp7atZXDg/Aohb0okI+itDuNfHc0K7PZQm 6 | 1UCcQgmr3ANyXlBy87mg16tZvzA1EuHOvPqgBwYFK4EEACOhgYkDgYYABADEgi0h 7 | RyKVT+O2AECjLyCDN/K01/O6B8kMkFduX4JCYdYC04bZmdjgLspgLenYi8ih6N20 8 | 0dCgVrKBmFqesPnGcwH3LmyQ4T8nULE8Sb1DI51+xpBcYPOecStuBHS+/bIbYvQC 9 | zpQajUcVxE+LFPfFU3jHSxHjR+7MCDGZe4E4KQbEeA== 10 | -----END EC PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /sign/cert/sample_rsa.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDEjCCAfoCCQDwMOjkYYOjPjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJV 3 | UzETMBEGA1UECBMKQ2FsaWZvcm5pYTETMBEGA1UEBxMKRXZlcnl3aGVyZTESMBAG 4 | A1UEAxMJbG9jYWxob3N0MB4XDTE0MDEwMjIxMjYxNloXDTE1MDEwMjIxMjYxNlow 5 | SzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEzARBgNVBAcTCkV2 6 | ZXJ5d2hlcmUxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQAD 7 | ggEPADCCAQoCggEBAOpCRECG7icpf0H37kuAM7s42oqggBoikoTpo5yapy+s5eFS 8 | p8HSqwhIYgZ4SghNLkj3e652SALav7chyZ2vWvitZycY+aq50n5UTTxDvdwsC5ZN 9 | eTycuzVWZALKGhV7VUPEhtWZNm0gruntronNa8l2WS0aF7P5SbhJ65SDQGprFFaY 10 | OSyN6550P3kqaAO7tDddcA1cmuIIDRf8tOIIeMkBFk1Qf+lh+3uRP2wztOTECSMR 11 | xX/hIkCe5DRFDK2MuDUyc/iY8IbY0hMFFGw5J7MWOwZLBOaZHX+Lf5lOYByPbMH7 12 | 8O0dda6T+tLYAVzsmJdHJFtaRguCaJVtSXKQUAMCAwEAATANBgkqhkiG9w0BAQUF 13 | AAOCAQEAi9HIM+FMfqXsRUY0rGxLlw403f3YtAG/ohzt5i8DKiKUG3YAnwRbL/Vz 14 | XLZaHru7XBC40wmKefKqoA0RHyNEddXgtY/aXzOlfTvp+xirop+D4DwJIbaj8/wH 15 | KWYGBucA/VgGY7JeSYYTUSuz2RoYtjPNRELIXN8A+D+nkJ3dxdFQ6jFfVfahN3nC 16 | IgRqRIOt1KaNI39CShccCaWJ5DeSASLXLPcEjrTi/pyDzC4kLF0VjHYlKT7lq5Rk 17 | MO6GeC+7YFvJtAyssM2nqunA2lUgyQHb1q4Ih/dcYOACubtBwW0ITpHz8N7eO+r1 18 | dtH/BF4yxeWl6p5kGLvuPXNU21ThgA== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /sign/cert/sample_rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA6kJEQIbuJyl/QffuS4AzuzjaiqCAGiKShOmjnJqnL6zl4VKn 3 | wdKrCEhiBnhKCE0uSPd7rnZIAtq/tyHJna9a+K1nJxj5qrnSflRNPEO93CwLlk15 4 | PJy7NVZkAsoaFXtVQ8SG1Zk2bSCu6e2uic1ryXZZLRoXs/lJuEnrlINAamsUVpg5 5 | LI3rnnQ/eSpoA7u0N11wDVya4ggNF/y04gh4yQEWTVB/6WH7e5E/bDO05MQJIxHF 6 | f+EiQJ7kNEUMrYy4NTJz+JjwhtjSEwUUbDknsxY7BksE5pkdf4t/mU5gHI9swfvw 7 | 7R11rpP60tgBXOyYl0ckW1pGC4JolW1JcpBQAwIDAQABAoIBAQDR4KT9muXruEYH 8 | eHXKtwWiC4HhCYM75xWWdyzatcRr9l/OzBr4kCIjvZX1bfgGEAAT54ar+9TwmcOB 9 | 84eK+vxqKBFu+fVVfD6WaDj6cUa858lJ1Ad2woUHeGsKBL/x1xnBKWYUILyecNXF 10 | R+lEFQMo8BtdSS9LU2E09yVVXXJs4+OtZZkWUXLBZwDNnr7sHd6wCU323q0iB8YV 11 | GPrm+MUTwskLQr8TI2eKLx+DZnnmwWQC4/m2pMoWU9bNuCetdsr7/0qOdSA0+W9/ 12 | 6DZtiCtM2i+j0EFwsIKmvFhSAN43yqTJ82OwA/3kV1boGIxpb0El2vBT5r5x2fDR 13 | m2gCKnUhAoGBAP4aWUlKukAvkz/otgBD8OEizqHKr7pXUX0KR8eAVtCwzmATSKmv 14 | eSGhnDaIFaFG8Q7GfHnZEWQPQeG6lH05AfLrGaH54igafGD3vjQv6Zc824V1Zlr6 15 | FYAr2006Pg1vdalv4GqVCFIyQ4MHuBhV0/tonuIUMgMLh3ZDhmAeaPR1AoGBAOwB 16 | /bfvdCoIwr94UeOuk171D7riTLslzVI+KKiHeF3MXr53gdGPwVr8nbSyocJtsXJL 17 | YzSempU/ckOSVjc4Rsm97Dcjfv+g+FW4V3fv///8RAmVJiUh7Ikm+g8Lm9dNt9pb 18 | MWMis1PT1vezMxMRDkdASeHXKZOxECjE6sI0R8OXAoGAFiWVwp2QvidnXhqaQkup 19 | ovW1rMRNrKAPqiZLO0gkSOwgEJ+dpax1kkjTpK0XtYbJW9BQ6Q3SRjZON65bYs6l 20 | LNbw1P1CiNxGGu181m+WnGfhejBRS3yggxtPcduxrNoaFzp5BNEYeJGI8NKraPOS 21 | Fkd4EZxQ/5Lxq40XGSmNXEECgYB5cATXclT5lAxxbT3lw9x8zehMJ0mIOslNekJ9 22 | hUeALDrMEr4v0KKTaMzVP91QKBVYVEY1uy6bgUwQTOoj+8cg46L6ND+S1YcyxGnt 23 | gwbc9zcjHyXDkZMed3nP6A9GV+00ZBwjgKV5itVgG0s6kloge4oItSfloQRJlhmd 24 | 2Dzc/QKBgQD279qBGf2+Kl0pknSUMDD6teXklsV94Sp9fnjpzSNCANfui7QvBAW3 25 | RtTTdClOvhG800VS3oAWHY645sGUSpk8vVisNcPYnjj4XSxK95qanxeIzhN9I0sd 26 | a2eJl2xhAFTfeToNXvGBoqQYLgv8aE/AUzq55fDYiAJZoTlOwjotDQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /status/status.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 European Digital Reading Lab. All rights reserved. 2 | // Licensed to the Readium Foundation under one or more contributor license agreements. 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 5 | 6 | package status 7 | 8 | import ( 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // List of status values as strings 14 | const ( 15 | STATUS_READY = "ready" 16 | STATUS_ACTIVE = "active" 17 | STATUS_REVOKED = "revoked" 18 | STATUS_RETURNED = "returned" 19 | STATUS_CANCELLED = "cancelled" 20 | STATUS_EXPIRED = "expired" 21 | EVENT_RENEWED = "renewed" 22 | ) 23 | 24 | // List of status values as int 25 | const ( 26 | STATUS_READY_INT = 0 27 | STATUS_ACTIVE_INT = 1 28 | STATUS_REVOKED_INT = 2 29 | STATUS_RETURNED_INT = 3 30 | STATUS_CANCELLED_INT = 4 31 | STATUS_EXPIRED_INT = 5 32 | EVENT_RENEWED_INT = 6 33 | ) 34 | 35 | // StatusValues defines status values logged in license status documents 36 | var StatusValues = map[int]string{ 37 | STATUS_READY_INT: STATUS_READY, 38 | STATUS_ACTIVE_INT: STATUS_ACTIVE, 39 | STATUS_REVOKED_INT: STATUS_REVOKED, 40 | STATUS_RETURNED_INT: STATUS_RETURNED, 41 | STATUS_CANCELLED_INT: STATUS_CANCELLED, 42 | STATUS_EXPIRED_INT: STATUS_EXPIRED, 43 | } 44 | 45 | // EventTypes defines additional event types. 46 | // It reuses all status values and adds one for renewed licenses. 47 | var EventTypes = map[int]string{ 48 | STATUS_ACTIVE_INT: "register", 49 | STATUS_REVOKED_INT: "revoke", 50 | STATUS_RETURNED_INT: "return", 51 | STATUS_CANCELLED_INT: "cancel", 52 | STATUS_EXPIRED_INT: "expire", 53 | EVENT_RENEWED_INT: "renew", 54 | } 55 | 56 | // GetStatus translates status number to status string 57 | func GetStatus(statusDB int64, status *string) { 58 | resultStr := reverse(strconv.FormatInt(statusDB, 2)) 59 | 60 | if count := strings.Count(resultStr, "1"); count == 1 { 61 | index := strings.Index(resultStr, "1") 62 | 63 | if len(StatusValues) >= index+1 { 64 | *status = StatusValues[index] 65 | } 66 | } 67 | } 68 | 69 | // SetStatus translates status string to status number 70 | func SetStatus(status string) (int64, error) { 71 | reg := make([]string, len(StatusValues)) 72 | 73 | for key := range StatusValues { 74 | if StatusValues[key] == status { 75 | reg[key] = "1" 76 | } else { 77 | reg[key] = "0" 78 | } 79 | } 80 | 81 | resultStr := reverse(strings.Join(reg[:], "")) 82 | 83 | statusDB, err := strconv.ParseInt(resultStr, 2, 64) 84 | return statusDB, err 85 | } 86 | 87 | func reverse(s string) string { 88 | r := []rune(s) 89 | for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { 90 | r[i], r[j] = r[j], r[i] 91 | } 92 | return string(r) 93 | } 94 | -------------------------------------------------------------------------------- /storage/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Readium Foundation 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, 4 | // are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this 7 | // list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright notice, 9 | // this list of conditions and the following disclaimer in the documentation and/or 10 | // other materials provided with the distribution. 11 | // 3. Neither the name of the organization nor the names of its contributors may be 12 | // used to endorse or promote products derived from this software without specific 13 | // prior written permission 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package storage 27 | 28 | import ( 29 | "errors" 30 | "io" 31 | ) 32 | 33 | // ErrNotFound is not found 34 | var ErrNotFound = errors.New("Item could not be found") 35 | 36 | // Item interface 37 | type Item interface { 38 | Key() string 39 | PublicURL() string 40 | Contents() (io.ReadCloser, error) 41 | } 42 | 43 | // Store interface 44 | type Store interface { 45 | Add(key string, r io.ReadSeeker) (Item, error) 46 | Get(key string) (Item, error) 47 | Remove(key string) error 48 | List() ([]Item, error) 49 | } 50 | -------------------------------------------------------------------------------- /storage/nostore.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Readium Foundation 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 storage 6 | 7 | import ( 8 | "io" 9 | ) 10 | 11 | // void storage, created to avoid breaking interfaces in case the storage is handled by the encryption tool. 12 | 13 | type noStorage struct { 14 | } 15 | 16 | type noItem struct { 17 | name string 18 | } 19 | 20 | // noItem functions 21 | 22 | func (i noItem) Key() string { 23 | return i.name 24 | } 25 | 26 | func (i noItem) PublicURL() string { 27 | return "" 28 | } 29 | 30 | func (i noItem) Contents() (io.ReadCloser, error) { 31 | return nil, ErrNotFound 32 | } 33 | 34 | // noStorage functions 35 | 36 | func (s noStorage) Add(key string, r io.ReadSeeker) (Item, error) { 37 | return &noItem{name: key}, nil 38 | } 39 | 40 | func (s noStorage) Get(key string) (Item, error) { 41 | return nil, ErrNotFound 42 | } 43 | 44 | func (s noStorage) Remove(key string) error { 45 | return ErrNotFound 46 | } 47 | 48 | func (s noStorage) List() ([]Item, error) { 49 | return nil, ErrNotFound 50 | } 51 | 52 | // NoStorage creates a new void storage 53 | // 54 | func NoStorage() Store { 55 | return noStorage{} 56 | } 57 | -------------------------------------------------------------------------------- /test/cert/cert-edrlab-test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFpTCCA42gAwIBAgIBATANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJGUjEO 3 | MAwGA1UEBxMFUGFyaXMxDzANBgNVBAoTBkVEUkxhYjESMBAGA1UECxMJTENQIFRl 4 | c3RzMSMwIQYDVQQDExpFRFJMYWIgUmVhZGl1bSBMQ1AgdGVzdCBDQTAeFw0xNjAz 5 | MjUwMzM3MDBaFw0yNjAzMjMwNzM3MDBaMIGQMQswCQYDVQQGEwJGUjEOMAwGA1UE 6 | BxMFUGFyaXMxDzANBgNVBAoTBkVEUkxhYjESMBAGA1UECxMJTENQIFRlc3RzMSIw 7 | IAYDVQQDExlUZXN0IHByb3ZpZGVyIGNlcnRpZmljYXRlMSgwJgYJKoZIhvcNAQkB 8 | FhlsYXVyZW50LmxlbWV1ckBlZHJsYWIub3JnMIICIjANBgkqhkiG9w0BAQEFAAOC 9 | Ag8AMIICCgKCAgEAq/gFXdvKb+EOzsEkHcoSOcPQmNzivzf+9NOJcxWi1/BwuxqA 10 | APv+4LKoLz89U1xx5TE1swL11BsEkIdVYrjl1RiYRa8YV4bb4xyMTm8lm39P16H1 11 | fG7Ep8yyoVuN6LT3WT2xHGp2jYU8I2nW78cyYApAWAuiMc3epeIOxC2mKgf1pGna 12 | X9j5l/Rx8hhxULqoHIHpR8e1eVRC7tgAz4Oy5qeLxGoL4S+GK/11eRlDO37whAWa 13 | MRbPnJDqqi8Z0Beovf6jmdoUTJdcPZZ9kFdtPsWjPNNHDldPuJBtCd7lupc0K4pC 14 | lJSqtJKyxs05Yeb1j7kbs/i3grdlUcxz0zOaPN1YzrzOO7GLEWUnIe+LwVXAeUse 15 | HedOexITyDQXXCqMoQw/BC6ApGzR0FynC6ojq98tStYGJAGbKBN/9p20CvYf4/hm 16 | PU3fFkImWguPIoeJT//0rz+nSynykeEVtORRIcdyOnX2rL03xxBW7qlTlUXOfQk5 17 | oLIWXBW9Z2Q63MPWi8jQhSI0jC12iEqCT54xKRHNWKr04at9pJL85M0bDCbBH/jJ 18 | +AIbVx02ewtXcWgWTgK9vgSPN5kRCwIGaV9PMS193KHfNpGqV45EKrfP8U2nvNDe 19 | yqLqAN5847ABSW7UmA5Kj/x5uGxIWu9MUKjZlT0FpepswFvMMo1InLHANMcCAwEA 20 | AaMyMDAwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYB 21 | BQUHAwEwDQYJKoZIhvcNAQELBQADggIBAEGAqzHsCbrfQwlWas3q66FG/xbiOYQx 22 | pngA4CZWKQzJJDyOFgWEihW+H6NlSIH8076srpIZByjEGXZfOku4NH4DGNOj6jQ9 23 | mEfEwbrvCoEVHQf5YXladXpKqZgEB2FKeJVjC7yplelBtjBpSo23zhG/o3/Bj7zR 24 | ySL6gUCewn7z/DkxM6AshDE4HKQxjxp7stpESev+0VTL813WXvwzmucr94H1VPra 25 | sFyVzQHj4Ib+Id1OAmgfzst0vSZyX6bjAuiN9yrs7wze5cAYTaswWr7GAnAZ/r1Z 26 | 3PiDp50qaGRhHqJ+lRAhihpFP+ZjsYWRqnxZnDzJkJ6RZAHi2a3VN8x5WhOUMTf3 27 | JZcFVheDmA4SaEjAZAHU8zUxx1Fstjc8GJcjTwWxCsVM2aREBKXAYDhPTVLRKt6P 28 | yQxB0GxjDZZSvGI9uXn6S5wvjuE4T2TUwbJeGHqJr4FNpXVQ2XNww+sV2QSiAwrl 29 | ORm8HNXqavj4rqz1PkUySXJ6b7zbjZoiACq4C7zb70tRYDyCfLTYtaTL3UK2Sa9e 30 | PSl0Fe6QfcqlGjalrqOo4GI6oqbAIkIXocHHksbLx0mIMSEWQOax+DqXhsl8tNGV 31 | wa5EiUSy83Sc0LyYXoWA35q8dugbkeNnY94rNG/hYKeci1VHhyg4rqxEeVwfBx12 32 | 1JqQSs+hHGKt 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /test/cert/privkey-edrlab-test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCr+AVd28pv4Q7O 3 | wSQdyhI5w9CY3OK/N/7004lzFaLX8HC7GoAA+/7gsqgvPz1TXHHlMTWzAvXUGwSQ 4 | h1ViuOXVGJhFrxhXhtvjHIxObyWbf0/XofV8bsSnzLKhW43otPdZPbEcanaNhTwj 5 | adbvxzJgCkBYC6Ixzd6l4g7ELaYqB/Wkadpf2PmX9HHyGHFQuqgcgelHx7V5VELu 6 | 2ADPg7Lmp4vEagvhL4Yr/XV5GUM7fvCEBZoxFs+ckOqqLxnQF6i9/qOZ2hRMl1w9 7 | ln2QV20+xaM800cOV0+4kG0J3uW6lzQrikKUlKq0krLGzTlh5vWPuRuz+LeCt2VR 8 | zHPTM5o83VjOvM47sYsRZSch74vBVcB5Sx4d5057EhPINBdcKoyhDD8ELoCkbNHQ 9 | XKcLqiOr3y1K1gYkAZsoE3/2nbQK9h/j+GY9Td8WQiZaC48ih4lP//SvP6dLKfKR 10 | 4RW05FEhx3I6dfasvTfHEFbuqVOVRc59CTmgshZcFb1nZDrcw9aLyNCFIjSMLXaI 11 | SoJPnjEpEc1YqvThq32kkvzkzRsMJsEf+Mn4AhtXHTZ7C1dxaBZOAr2+BI83mREL 12 | AgZpX08xLX3cod82kapXjkQqt8/xTae80N7KouoA3nzjsAFJbtSYDkqP/Hm4bEha 13 | 70xQqNmVPQWl6mzAW8wyjUicscA0xwIDAQABAoICAHRUYeT9InMOToMEWlcgc7XJ 14 | xZkyityJl5msY2WLdE4ZI1kecwq3bNI5aNYVNHnopk9BO68tyHfEExFlyxfuNeMy 15 | MQeqi4/h9Wsry6nfPBR1SeB3eeXD1JoiOZA3q8aIbG5c8itjxXC0eVsHZNNs76HS 16 | LXah4AVK21thkVWZ/7adDck7pfKCh00ImfAIItdWfRRTuSfKa/N4UZLaYan+0A2B 17 | t2WVyxV4nY2ydj4GHdnoY/EbDRQ59xVVXrjjBdYjuw1TbP5NbL/nAAnVfHkg+xCk 18 | h2lFDuMfoxkZ8fjkZIkVccAmViXGb7eZKQCcoT2eC+0+bgQPsqvlPc/Yu11aofGA 19 | WJXSb9VbPnNWDCmDfdn03VcHBCCLYLyEBHogw5Dw+V17vV9Q5aN3aqDnbLRE0/22 20 | +rmBnpn+Ej0hxDZz4jFzpOtACT6vRyrzWQ0IvKnb8DzdTEE3p0Nm5lQzpMSkWLiz 21 | u+gzTTRo3qMQN0geOMZjeOE+p0R7m+QRpPKcBGpMkOzmv2NLoLBonGilIQhmNgNv 22 | ndxVRnD/sxRyV/tToq1KpVFHFDVfOme9fYYsi7Adviy4bqXqFgX6rpk/eXzFGyWV 23 | QNDvyoTu6gjKTduClV0mzmDqF6P6zyNWEcNQe4pTyrk3DK7WMbYDRMyz5j3wxk/1 24 | urp/bcSXihsD4zgcYHfBAoIBAQDYAZpGR9EoNvRpc19MCFdU438Zz5oOPgUFUZk1 25 | YfL7csJBXGdBnALsiwyzKDd/fgXw3UF8B+xRxo6bLqTcXZYiQI3OnFm4Rz4HoqVl 26 | MlTHO+jarJXLjtbISPlz115pRciOchzSd+D3IvgfdZdWH0P0EcUv9mfVYDYgf923 27 | iLxML2OO7pkOi4tMyq/7tKUZCLC5bREzy+AsQRafvtdY70w4GW+KLXh7h9q1dZkc 28 | pwjUttgg2w+GNLQ7eM661yUvCDP17wEDtkd0fYPpQJBZaY5Zi+0mB30waVoOAbz1 29 | nBmGw55uess8b1qBPZC4j8RzncOCSLK5657e8mVBmIHq9BLnAoIBAQDLzxryCBhH 30 | TeWCvEqv/WiuworY1z1rYF52ZxWkwubZ0l+yFDE9xsfNidVBshW3JPjpDK+bvZp0 31 | lrgQKspvfgbk3s5RoTKmuhSIxSysc1gF/Pudr0Nn1tyq60kKEqEOsJ0lLc3IPGiR 32 | WTUcEkQve+rsvuoCLXaNN8aQ9e7KrUBXtogoz7zzgI8LJ7kvoPrpOjkPHoxt5jMG 33 | H0Vxf7jKY77K9Z87vzS4do/QXMW3xku04WmXPJUdw6MdinJH077HANS8URlGMdKS 34 | 8KWV26xtxD2dYmrZFeuqWMuaNkD1zFT9ARhhZVWBacEZyCiJMMzpMCaLdVzH25aB 35 | eNzPzQXwVHMhAoIBAQCpJrAiIzE2FbrWLSPOTaXd8vzgQIM6bfTOknSwqhB2OgrG 36 | 1k6BwWNEyyrZzT+Qliae1RTnxOiZrSyVdzRg4OSl0/l4d1WHCjsYDZUJpx1em5qg 37 | S5BORIALfaHixh/Hvn7slY3ef0PrJDY7pIErTvaO1a+d/I6ZOGuKjbcZd3lFC8Mh 38 | 9dRj4IDoVLz1FiRHc1e5DSDwd9IHtby6wd6vZU1BjSGcijkUO3HYscuB9Yfj039y 39 | UzlX9kPBYrw48ivLJdhS2aPovUA9h+DZstaXPiLcGshOBIVTeNytpUzM5T3UG7Ig 40 | gAZRQD4WgRiofuTvSPcGOlnDYDEcE6OZd40daZ63AoIBAClXk5GxDqu4I2xHoabd 41 | cpSwpdWio7TaUY8XDg7j8kwRHpRTAEIVxoXtBbNT2o/SEiPEDQM8jM5FnIS7CWFg 42 | YSmgAJ4SYuHrMSslRBCfPSXJ0YiE+tlffle1bpV6fe0q6lWWWv3ZMyT4wYs2MVqo 43 | elkXyQQ0EK5IuCDDonK+fiTMxAXzTdLfKyUKI8iDkSIqS1TDz5yja4o8YavKSsPT 44 | Q+qLtIc8HAenFTkg+IJ02PqSFwc0pNLq0nW1lcT4MOTRSFYC5WuVw69G3W/upaWG 45 | fzEju6TZi3p8lKBtjPPPRRYlWLq5AYVE3ITMU1yw54AN4mO7pNxtsLQGvlPwtNmx 46 | Z8ECggEAdtAGytCXo9nLzDa6IF2S1LKUQL2Bf6Ta5xSBZ0xW20kZ0f9KA786LxSN 47 | YUMYkVpBpRm3nkHJUuMnORPb+hhQQjPxaJ0rxewKE1hEyl+J8lgxd9v/T1ct8oy8 48 | eO+uVa8Tv4HRJGQSdQ9Z1fS+MlCVqBd6C7qmn8sXvMDzNkIAQOSP14qeBnQlgMcB 49 | QT030thWYxwQXdFr7ZyDYlYy09k7+k3UVBDShXEq2EpG4JsrwpmWcuYerob2al/K 50 | taIicoRzjRyQvRs9pGjXs82hT1/MyMgmcRYWpyE5IKGZBo867tIZw8T6cTtzpvFv 51 | sXxINdM+KazdbsK/GIJ6hMvuL6MzOg== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /test/config.yaml: -------------------------------------------------------------------------------- 1 | # Test configuration for lcpserver and lsdserver. 2 | # It is meant to be used as a quick-start setup. 3 | # 4 | # Replace every occurence of : by the License Server host name or IP address + port. 5 | # Replace every occurence of by the Status Server host name. 6 | # Replace every occurence of by the License Gateway host name. 7 | # Replace every occurence of by the absolute path to the folder hosting every file associated with the LCP service. 8 | 9 | # Shared configuration 10 | 11 | # The usernames and passwords must match the ones in the htpasswd files for each server. 12 | 13 | lcp: 14 | # the public url a client app will use to access the License Server (optional) 15 | public_base_url: "http://:" 16 | lcp_update_auth: 17 | # login and password used by the Status Server to access the License Server 18 | username: "" 19 | password: "" 20 | 21 | lsd: 22 | # the public url a client app will use to access the Status Server 23 | public_base_url: "https://" 24 | lsd_notify_auth: 25 | # login and password used by the License Server to access the Status Server 26 | username: "" 27 | password: "" 28 | 29 | 30 | # LCP Server 31 | 32 | profile: "basic" 33 | lcp: 34 | # the port on which the License Server will be running 35 | port: 36 | # replace this dsn if you're not using SQLite 37 | database: "sqlite3://file:/db/lcp.sqlite?cache=shared&mode=rwc" 38 | # authentication file of the License Server. Here we use the same file for the License Server and Status Server 39 | auth_file: "/config/htpasswd" 40 | # uncomment if lcpencrypt does not manage the storage of encrypted publications 41 | #storage: 42 | # filesystem: 43 | # directory: "/files/storage" 44 | certificate: 45 | # theses test certificates are provided in the test/cert folder of the codebase 46 | cert: "/config/cert-edrlab-test.pem" 47 | private_key: "/config/privkey-edrlab-test.pem" 48 | license: 49 | links: 50 | # leave the url as-is (after has been resolved) 51 | status: "http:///licenses/{license_id}/status" 52 | # the url of a REAL html page, that indicates how the user can get back his passphrase if forgotten 53 | hint: "" 54 | 55 | 56 | # LSD Server 57 | 58 | lsd: 59 | # the port on which the Status Server will be running 60 | port: 61 | # replace this dsn if you're not using SQLite 62 | database: "sqlite3:///db/lsd.sqlite?cache=shared&mode=rwc" 63 | # authentication file of the Status Server. Here we use the same file for the License Server and Status Server 64 | auth_file: "/config/htpasswd" 65 | # in this example, the License Gateway is developed so that adding a license id 66 | # to the host name gives access to a fresh license. 67 | # Keep {license_id} as-is; this is a template. 68 | # Read the doc to know more about how to develop a License Gateway. 69 | license_link_url: "http:///{license_id}" 70 | license_status: 71 | register: true 72 | # uncomment the lines below if you're allowing e-lending 73 | #renew: true 74 | #return: true 75 | #renting_days: 60 76 | #renew_days: 7 77 | -------------------------------------------------------------------------------- /test/samples/lorem.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readium/readium-lcp-server/e2c484f571a8013faf13a335c007890150751d79/test/samples/lorem.epub -------------------------------------------------------------------------------- /test/samples/sample-with-space.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readium/readium-lcp-server/e2c484f571a8013faf13a335c007890150751d79/test/samples/sample-with-space.epub -------------------------------------------------------------------------------- /test/samples/sample.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readium/readium-lcp-server/e2c484f571a8013faf13a335c007890150751d79/test/samples/sample.epub --------------------------------------------------------------------------------