├── .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 |
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 |
--------------------------------------------------------------------------------
/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 | {{user.userID}} |
15 | {{user.alias}} |
16 | hashed passphrase = {{user.password}} |
17 | |
18 |
19 |
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 |
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 | 0">
9 |
Best Sellers
10 |
11 |
12 |
13 | Title |
14 | Nbr |
15 |
16 |
17 |
18 | 0">{{pub.title}} |
19 | 0">{{pub.count}} |
20 |
21 |
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 | UUID |
9 |
10 | Publication
11 |
12 |
13 | |
14 |
15 | User
16 |
17 |
18 | |
19 |
20 | Type
21 |
22 |
23 | |
24 |
25 | Devices
26 |
27 |
28 | |
29 |
30 | Statues
31 |
32 |
33 | |
34 | Message |
35 | |
36 |
37 |
38 |
39 | {{license.id}} |
40 | {{license.publicationTitle}} |
41 | {{license.userName}} |
42 | {{license.type}} |
43 | {{license.devices}} |
44 | {{license.status}} |
45 | {{license.message}} |
46 |
47 |
50 | Status
51 |
52 | |
53 |
54 |
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 |
4 | - Publications
5 | - Add a publication
6 |
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 |
5 | - Publications
6 | - Edit {{publication.title}}
7 |
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 |
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 |
9 |
10 |
11 | 0" class="table">
12 |
13 |
14 |
15 | #
16 |
17 |
18 | |
19 |
20 | Title
21 |
22 |
23 | |
24 |
25 | UUID
26 |
27 |
28 | |
29 | Actions |
30 |
31 |
32 |
33 |
34 | {{publication.id}} |
35 | {{publication.title}} |
36 | {{publication.uuid}} |
37 |
38 |
43 |
45 | Edit
46 |
47 | |
48 |
49 |
50 |
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 |
4 | - Purchases
5 | - Add a purchase
6 |
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 |
5 | - Purchases
6 | - Renew purchase
7 |
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 |
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 |
5 | - Purchases
6 | - Purchase {{ purchase.id }}
7 |
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 |
58 |
59 |
60 |
Best Sellers
61 |
62 |
63 |
64 | Id |
65 | Nbr |
66 |
67 |
68 |
69 | {{}} |
70 | {{}} |
71 |
72 |
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 |
4 | - Users
5 | - Add a user
6 |
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 |
5 | - Users
6 | - {{user.email}}
7 |
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 |
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 |
11 |
12 |
13 | 0" class="table">
14 |
15 |
16 |
17 | #
18 |
19 |
20 | |
21 |
22 | Name
23 |
24 |
25 | |
26 |
27 | Email
28 |
29 |
30 | |
31 |
32 | Hint
33 |
34 |
35 | |
36 | Actions |
37 |
38 |
39 |
40 |
41 | {{user.id}} |
42 | {{user.name}} |
43 | {{user.email}} |
44 | {{user.hint}} |
45 |
46 |
51 |
53 | Edit
54 |
55 | |
56 |
57 |
58 |
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
--------------------------------------------------------------------------------