├── .gitignore ├── LICENSE ├── README.adoc ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── superbiz │ │ ├── moviefun │ │ ├── Comment.java │ │ ├── LoadBalancerRegisterService.java │ │ ├── Movie.java │ │ ├── MoviesBean.java │ │ ├── rest │ │ │ ├── ApplicationConfig.java │ │ │ ├── Language.java │ │ │ ├── LoadDataResource.java │ │ │ ├── MoviesMPJWTConfigurationProvider.java │ │ │ ├── MoviesResource.java │ │ │ └── Person.java │ │ ├── sts │ │ │ ├── MovieClaimsSourceResource.java │ │ │ └── UserPreferences.java │ │ └── utils │ │ │ ├── Cipher.java │ │ │ ├── CipherProduces.java │ │ │ ├── DecryptedValue.java │ │ │ └── TokenUtil.java │ │ └── rest │ │ └── GreetingResource.java ├── resources │ ├── META-INF │ │ └── persistence.xml │ ├── account-test.json │ ├── privateKey-pkcs1.pem │ ├── privateKey-pkcs8.pem │ ├── profile-oauth2.json │ ├── publicKey.pem │ └── route.json ├── tag │ └── import │ │ ├── account-alex.json │ │ ├── account-john.json │ │ ├── account-mark.json │ │ ├── account-nick.json │ │ ├── account-test.json │ │ ├── api-claims-movies.json │ │ ├── api-movies.json │ │ ├── default-keysize-2048.json │ │ ├── default-oauth2-profile.json │ │ ├── movies-admin.json │ │ ├── profile-oauth2-movies.json │ │ └── route-movies.json ├── tomee-backup │ └── resources.xml ├── tomee │ └── tomee.xml └── webapp │ ├── WEB-INF │ └── web.xml │ ├── app │ ├── app.less │ ├── config.js │ ├── img │ │ ├── login.jpg │ │ ├── logout-icon.png │ │ ├── movie-icon.png │ │ └── movie-logo.png │ └── js │ │ ├── app.js │ │ ├── model │ │ ├── auth.js │ │ ├── login.js │ │ ├── movie.js │ │ └── movies.js │ │ ├── start.js │ │ ├── templates.js │ │ ├── templates │ │ ├── alert.handlebars │ │ ├── container.handlebars │ │ ├── load-data-link.handlebars │ │ ├── login.handlebars │ │ ├── main-table-paginator-button.handlebars │ │ ├── main-table-row.handlebars │ │ ├── main.handlebars │ │ ├── movie-page.handlebars │ │ └── movie.handlebars │ │ ├── tools │ │ ├── alert.view.js │ │ ├── date.js │ │ ├── gravatar.js │ │ ├── header-wrapper.js │ │ ├── http-signatures-js.umd.min.js │ │ ├── i18n.js │ │ ├── id.js │ │ ├── jwk-js.umd.min.js │ │ └── log.js │ │ └── view │ │ ├── container.js │ │ ├── login.js │ │ ├── main-table-paginator.js │ │ ├── main-table-row.js │ │ ├── main.js │ │ ├── movie-page.view.js │ │ └── movie.js │ └── index.jsp └── test ├── java └── org │ └── superbiz │ ├── moviefun │ ├── MoviesTest.java │ ├── STSResource.java │ ├── TokenErrorResponse.java │ └── TokenResponse.java │ └── rest │ └── GreetingResourceTest.java └── resources ├── META-INF └── application-client.xml ├── Token1.json ├── Token2.json └── arquillian.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | .idea 25 | *.iml 26 | target/ 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | # Microprofile JWT Movie Chat 2 | 3 | This web application shows how to use Microprofile JWT to secure your microservices. 4 | It uses Apache TomEE as the default Microprofile implementation. 5 | 6 | # OAuth2 + JWT (STS) 7 | 8 | In order to work, this application requires an STS capable of generating signed JWT tokens. 9 | We use Tribestream API Gateway as it's a complete Security Token Service. 10 | It's also capable of doing routing and while enforcing permissions. 11 | If you want to know more, check out the website at https://tribestream.io 12 | 13 | To start the Tribestream API Gateway on a terminal execute the following command: 14 | 15 | mvn tag:run 16 | 17 | The following message indicates the Tribestream API Gateway is up and running: 18 | 19 | [INFO] Tribestream started http://localhost:8080/tag/ 20 | 21 | You can login now using on a browser the following URL: http://localhost:8080/tag/ using `admin` for both the username and password. 22 | 23 | # Start the movie chat application 24 | 25 | On a new terminal execute the following command: 26 | 27 | 28 | mvn clean install -DskipTests tomee:run 29 | 30 | The following message indicates the application is up and running: 31 | 32 | 08-Mar-2019 11:34:07.760 INFO [main] sun.reflect.NativeMethodAccessorImpl.invoke Starting ProtocolHandler ["http-nio-8181"] 33 | 08-Mar-2019 11:34:07.765 INFO [main] sun.reflect.NativeMethodAccessorImpl.invoke Starting ProtocolHandler ["ajp-nio-8010"] 34 | 08-Mar-2019 11:34:07.767 INFO [main] sun.reflect.NativeMethodAccessorImpl.invoke Server startup in 6786 ms 35 | 36 | 37 | You can login now using on a browser the following URL: http://localhost:8080/movies 38 | 39 | You can test any of the following accounts: 40 | - Account `alex` with password `password` with create, update and delete role. 41 | - Account `john` with password `password` with just comments add role. 42 | 43 | For deeper exaplanation of Microprofile JWT using this application access: https://tribestream.io/guide/en/api-gateway/microservice-security-microprofile-jwt/current/ 44 | -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/Comment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun; 18 | 19 | import javax.persistence.Entity; 20 | import javax.persistence.GeneratedValue; 21 | import javax.persistence.GenerationType; 22 | import javax.persistence.Id; 23 | import javax.persistence.Temporal; 24 | import javax.persistence.TemporalType; 25 | import javax.xml.bind.annotation.XmlRootElement; 26 | import java.util.Date; 27 | 28 | @Entity 29 | @XmlRootElement(name = "comment") 30 | public class Comment { 31 | @Id 32 | @GeneratedValue(strategy = GenerationType.AUTO) 33 | private long id; 34 | 35 | private String author; 36 | private String email; 37 | private String comment; 38 | 39 | @Temporal(value = TemporalType.TIMESTAMP) 40 | private Date timestamp; 41 | 42 | public String getAuthor() { 43 | return author; 44 | } 45 | 46 | public void setAuthor(final String author) { 47 | this.author = author; 48 | } 49 | 50 | public String getComment() { 51 | return comment; 52 | } 53 | 54 | public void setComment(final String comment) { 55 | this.comment = comment; 56 | } 57 | 58 | public long getId() { 59 | return id; 60 | } 61 | 62 | public void setId(final long id) { 63 | this.id = id; 64 | } 65 | 66 | public Date getTimestamp() { 67 | return timestamp; 68 | } 69 | 70 | public void setTimestamp(final Date timestamp) { 71 | this.timestamp = timestamp; 72 | } 73 | 74 | 75 | public String getEmail() { 76 | return email; 77 | } 78 | 79 | public void setEmail(final String email) { 80 | this.email = email; 81 | } 82 | } -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/Movie.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun; 18 | 19 | import javax.persistence.CascadeType; 20 | import javax.persistence.Entity; 21 | import javax.persistence.FetchType; 22 | import javax.persistence.GeneratedValue; 23 | import javax.persistence.GenerationType; 24 | import javax.persistence.Id; 25 | import javax.persistence.OneToMany; 26 | import javax.xml.bind.annotation.XmlRootElement; 27 | import java.util.Set; 28 | 29 | @Entity 30 | @XmlRootElement(name = "movie") 31 | public class Movie { 32 | @Id 33 | @GeneratedValue(strategy = GenerationType.AUTO) 34 | private long id; 35 | 36 | private String director; 37 | private String title; 38 | private int year; 39 | private String genre; 40 | private int rating; 41 | 42 | @OneToMany(targetEntity = Comment.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true) 43 | private Set comments; 44 | 45 | public Movie() { 46 | } 47 | 48 | public Movie(String title, String director, String genre, int rating, int year) { 49 | this.director = director; 50 | this.title = title; 51 | this.year = year; 52 | this.genre = genre; 53 | this.rating = rating; 54 | } 55 | 56 | public Movie(String director, String title, int year) { 57 | this.director = director; 58 | this.title = title; 59 | this.year = year; 60 | } 61 | 62 | public long getId() { 63 | return id; 64 | } 65 | 66 | public void setId(long id) { 67 | this.id = id; 68 | } 69 | 70 | public String getDirector() { 71 | return director; 72 | } 73 | 74 | public void setDirector(String director) { 75 | this.director = director; 76 | } 77 | 78 | public String getTitle() { 79 | return title; 80 | } 81 | 82 | public void setTitle(String title) { 83 | this.title = title; 84 | } 85 | 86 | public int getYear() { 87 | return year; 88 | } 89 | 90 | public void setYear(int year) { 91 | this.year = year; 92 | } 93 | 94 | public String getGenre() { 95 | return genre; 96 | } 97 | 98 | public void setGenre(String genre) { 99 | this.genre = genre; 100 | } 101 | 102 | public int getRating() { 103 | return rating; 104 | } 105 | 106 | public void setRating(int rating) { 107 | this.rating = rating; 108 | } 109 | 110 | public Set getComments() { 111 | return comments; 112 | } 113 | 114 | public void setComments(final Set comments) { 115 | this.comments = comments; 116 | } 117 | } -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/MoviesBean.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun; 18 | 19 | import javax.ejb.Lock; 20 | import javax.ejb.LockType; 21 | import javax.ejb.Singleton; 22 | import javax.persistence.EntityManager; 23 | import javax.persistence.PersistenceContext; 24 | import javax.persistence.TypedQuery; 25 | import javax.persistence.criteria.CriteriaBuilder; 26 | import javax.persistence.criteria.CriteriaQuery; 27 | import javax.persistence.criteria.Path; 28 | import javax.persistence.criteria.Predicate; 29 | import javax.persistence.criteria.Root; 30 | import javax.persistence.metamodel.EntityType; 31 | import java.util.List; 32 | 33 | @Singleton 34 | @Lock(LockType.READ) 35 | public class MoviesBean { 36 | 37 | @PersistenceContext(unitName = "movie-unit") 38 | private EntityManager entityManager; 39 | 40 | public Movie find(Long id) { 41 | return entityManager.find(Movie.class, id); 42 | } 43 | 44 | public Movie addMovie(Movie movie) { 45 | entityManager.persist(movie); 46 | return movie; 47 | } 48 | 49 | public void editMovie(Movie movie) { 50 | entityManager.merge(movie); 51 | } 52 | 53 | public void deleteMovie(long id) { 54 | Movie movie = entityManager.find(Movie.class, id); 55 | if (movie == null) { 56 | throw new IllegalArgumentException("Movie " + id + " not found"); 57 | } 58 | entityManager.remove(movie); 59 | } 60 | 61 | public Movie addCommentToMovie(final Long id, final Comment comment) { 62 | final Movie movie = entityManager.find(Movie.class, id); 63 | if (movie == null) { 64 | throw new IllegalArgumentException("Movie " + id + " not found"); 65 | } 66 | entityManager.persist(comment); 67 | movie.getComments().add(comment); 68 | entityManager.merge(movie); 69 | return movie; 70 | } 71 | 72 | public Movie removeCommentToMovie(final Long id, final Comment comment) { 73 | final Movie movie = entityManager.find(Movie.class, id); 74 | if (movie == null) { 75 | throw new IllegalArgumentException("Movie " + id + " not found"); 76 | } 77 | movie.getComments().remove(comment); 78 | entityManager.remove(comment); 79 | entityManager.merge(movie); 80 | return movie; 81 | } 82 | 83 | public List getMovies(Integer firstResult, Integer maxResults, String field, String searchTerm) { 84 | CriteriaBuilder qb = entityManager.getCriteriaBuilder(); 85 | CriteriaQuery cq = qb.createQuery(Movie.class); 86 | Root root = cq.from(Movie.class); 87 | EntityType type = entityManager.getMetamodel().entity(Movie.class); 88 | if (field != null && searchTerm != null && !"".equals(field.trim()) && !"".equals(searchTerm.trim())) { 89 | Path path = root.get(type.getDeclaredSingularAttribute(field.trim(), String.class)); 90 | Predicate condition = qb.like(path, "%" + searchTerm.trim() + "%"); 91 | cq.where(condition); 92 | } 93 | TypedQuery q = entityManager.createQuery(cq); 94 | if (maxResults != null) { 95 | q.setMaxResults(maxResults); 96 | } 97 | if (firstResult != null) { 98 | q.setFirstResult(firstResult); 99 | } 100 | return q.getResultList(); 101 | } 102 | 103 | public int count(String field, String searchTerm) { 104 | CriteriaBuilder qb = entityManager.getCriteriaBuilder(); 105 | CriteriaQuery cq = qb.createQuery(Long.class); 106 | Root root = cq.from(Movie.class); 107 | EntityType type = entityManager.getMetamodel().entity(Movie.class); 108 | cq.select(qb.count(root)); 109 | if (field != null && searchTerm != null && !"".equals(field.trim()) && !"".equals(searchTerm.trim())) { 110 | Path path = root.get(type.getDeclaredSingularAttribute(field.trim(), String.class)); 111 | Predicate condition = qb.like(path, "%" + searchTerm.trim() + "%"); 112 | cq.where(condition); 113 | } 114 | return entityManager.createQuery(cq).getSingleResult().intValue(); 115 | } 116 | 117 | public void clean() { 118 | entityManager.createQuery("delete from Movie").executeUpdate(); 119 | } 120 | } -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/rest/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.rest; 18 | 19 | import org.eclipse.microprofile.auth.LoginConfig; 20 | 21 | import javax.ws.rs.ApplicationPath; 22 | import javax.ws.rs.core.Application; 23 | 24 | @ApplicationPath("/rest") 25 | @LoginConfig(authMethod = "MP-JWT") 26 | public class ApplicationConfig extends Application { 27 | // let the server discover the endpoints 28 | } -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/rest/Language.java: -------------------------------------------------------------------------------- 1 | package org.superbiz.moviefun.rest; 2 | 3 | public enum Language { 4 | SPANISH, 5 | ENGLISH; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/rest/LoadDataResource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.rest; 18 | 19 | import com.github.javafaker.Faker; 20 | import org.superbiz.moviefun.Comment; 21 | import org.superbiz.moviefun.Movie; 22 | import org.superbiz.moviefun.MoviesBean; 23 | 24 | import javax.ejb.EJB; 25 | import javax.ws.rs.POST; 26 | import javax.ws.rs.Path; 27 | import java.time.temporal.TemporalField; 28 | import java.util.Locale; 29 | import java.util.Random; 30 | import java.util.concurrent.TimeUnit; 31 | 32 | @Path("load") 33 | public class LoadDataResource { 34 | 35 | @EJB 36 | private MoviesBean moviesBean; 37 | 38 | @POST 39 | public void load() { 40 | final Faker faker = new Faker(Locale.ENGLISH); 41 | final Random random = new Random(System.nanoTime()); 42 | 43 | for (int i = 0 ; i < (5 + random.nextInt(20)) ; i++) { 44 | addComments( 45 | moviesBean.addMovie( 46 | new Movie( 47 | faker.book().title(), 48 | faker.book().author(), 49 | faker.book().genre(), 50 | random.nextInt(10), 51 | 1960 + random.nextInt(50))),random.nextInt(100)); 52 | } 53 | 54 | } 55 | 56 | private void addComments(final Movie movie, final int nbComments) { 57 | 58 | final Faker faker = new Faker(Locale.ENGLISH); 59 | 60 | for (int i = 0; i < nbComments; i++) { 61 | final Comment comment = new Comment(); 62 | comment.setTimestamp(faker.date().past(300, TimeUnit.DAYS)); 63 | comment.setAuthor(faker.name().fullName()); 64 | comment.setEmail(faker.internet().emailAddress()); 65 | comment.setComment(faker.chuckNorris().fact()); 66 | 67 | moviesBean.addCommentToMovie(movie.getId(), comment); 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/rest/MoviesMPJWTConfigurationProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.rest; 18 | 19 | import java.security.KeyFactory; 20 | import java.security.interfaces.RSAPublicKey; 21 | import java.security.spec.X509EncodedKeySpec; 22 | import java.util.Optional; 23 | 24 | import javax.enterprise.context.Dependent; 25 | import javax.enterprise.inject.Produces; 26 | import org.apache.tomee.microprofile.jwt.config.JWTAuthConfiguration; 27 | import org.superbiz.moviefun.utils.TokenUtil; 28 | 29 | @Dependent 30 | public class MoviesMPJWTConfigurationProvider { 31 | 32 | public static final String ISSUED_BY = "/oauth2/token"; 33 | 34 | @Produces 35 | Optional getOptionalContextInfo() throws Exception { 36 | 37 | byte[] encodedBytes = TokenUtil.readPublicKey("/publicKey.pem").getEncoded(); 38 | 39 | final X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes); 40 | final KeyFactory kf = KeyFactory.getInstance("RSA"); 41 | final RSAPublicKey pk = (RSAPublicKey) kf.generatePublic(spec); 42 | 43 | JWTAuthConfiguration contextInfo = JWTAuthConfiguration.authContextInfo(pk, ISSUED_BY); 44 | 45 | return Optional.of(contextInfo); 46 | } 47 | 48 | @Produces 49 | JWTAuthConfiguration getContextInfo() throws Exception { 50 | return getOptionalContextInfo().get(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/rest/MoviesResource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.rest; 18 | 19 | import org.apache.commons.lang3.StringUtils; 20 | import org.eclipse.microprofile.jwt.Claim; 21 | import org.eclipse.microprofile.jwt.ClaimValue; 22 | import org.eclipse.microprofile.jwt.JsonWebToken; 23 | import org.superbiz.moviefun.Comment; 24 | import org.superbiz.moviefun.Movie; 25 | import org.superbiz.moviefun.MoviesBean; 26 | import org.superbiz.moviefun.utils.DecryptedValue; 27 | 28 | import javax.annotation.security.RolesAllowed; 29 | import javax.ejb.EJB; 30 | import javax.enterprise.context.ApplicationScoped; 31 | import javax.inject.Inject; 32 | import javax.ws.rs.Consumes; 33 | import javax.ws.rs.DELETE; 34 | import javax.ws.rs.GET; 35 | import javax.ws.rs.POST; 36 | import javax.ws.rs.PUT; 37 | import javax.ws.rs.Path; 38 | import javax.ws.rs.PathParam; 39 | import javax.ws.rs.Produces; 40 | import javax.ws.rs.QueryParam; 41 | import javax.ws.rs.WebApplicationException; 42 | import javax.ws.rs.core.Context; 43 | import javax.ws.rs.core.MediaType; 44 | import javax.ws.rs.core.Response; 45 | import javax.ws.rs.core.SecurityContext; 46 | import java.util.Date; 47 | import java.util.List; 48 | import java.util.logging.Logger; 49 | 50 | @Path("movies") 51 | @Produces({"application/json"}) 52 | @ApplicationScoped 53 | public class MoviesResource { 54 | 55 | private static final Logger LOGGER = Logger.getLogger(MoviesResource.class.getName()); 56 | 57 | @EJB 58 | private MoviesBean service; 59 | 60 | @Inject 61 | @Claim("username") 62 | private ClaimValue username; 63 | 64 | @Inject 65 | @Claim("email") 66 | private ClaimValue email; 67 | 68 | @Inject 69 | @Claim("jti") 70 | private ClaimValue jti; 71 | 72 | 73 | @Inject 74 | @DecryptedValue("creditCard") 75 | private String creditCard; 76 | 77 | @Inject 78 | private Person person; 79 | 80 | @Inject 81 | private JsonWebToken jwtPrincipal; 82 | 83 | @Context 84 | private SecurityContext securityContext; 85 | 86 | @GET 87 | @Path("{id}") 88 | public Movie find(@PathParam("id") Long id) { 89 | LOGGER.info("find: " + toIdentityString()); 90 | return service.find(id); 91 | } 92 | 93 | private String toIdentityString() { 94 | if (jwtPrincipal == null) { 95 | return "no authenticated user."; 96 | } 97 | 98 | final StringBuilder builder = new StringBuilder(); 99 | 100 | builder.append(username); 101 | builder.append(String.format(" (jti=%s)", jti)); 102 | builder.append(String.format(" (email=%s)", email)); 103 | builder.append(String.format(" (person creditCard=%s)", person.getCreditCard())); 104 | builder.append(String.format(" (language=%s)", person.getLanguage())); 105 | builder.append(String.format(" (groups=%s)", StringUtils.join(jwtPrincipal.getGroups(), ", "))); 106 | return builder.toString(); 107 | } 108 | 109 | @GET 110 | public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, 111 | @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { 112 | LOGGER.info("list: " + toIdentityString()); 113 | return service.getMovies(first, max, field, searchTerm); 114 | } 115 | 116 | @POST 117 | @Consumes("application/json") 118 | public Movie addMovie(Movie movie) { 119 | LOGGER.info("add: " + toIdentityString()); 120 | if (!securityContext.isUserInRole("create")) { 121 | throw new WebApplicationException("Bad permission.", Response.Status.FORBIDDEN); 122 | } 123 | service.addMovie(movie); 124 | return movie; 125 | } 126 | 127 | @POST 128 | @Path("{id}/comment") 129 | @Consumes("text/plain") 130 | public Movie addCommentToMovie( 131 | @PathParam("id") final long id, 132 | final String comment) { 133 | 134 | if (jwtPrincipal == null) { 135 | throw new WebApplicationException("Authentication required.", Response.Status.UNAUTHORIZED); 136 | } 137 | LOGGER.info("add comment to movie: " + toIdentityString()); 138 | 139 | final Comment c = new Comment(); 140 | c.setAuthor(username.getValue()); 141 | c.setComment(comment); 142 | c.setEmail(email.getValue()); 143 | c.setTimestamp(new Date()); 144 | 145 | return service.addCommentToMovie(id, c); 146 | } 147 | 148 | @PUT 149 | @Path("{id}") 150 | @Consumes("application/json") 151 | @RolesAllowed("update") 152 | public Movie editMovie( 153 | @PathParam("id") final long id, 154 | Movie movie) { 155 | LOGGER.info("edit: " + toIdentityString()); 156 | service.editMovie(movie); 157 | return movie; 158 | } 159 | 160 | @DELETE 161 | @Path("{id}") 162 | @RolesAllowed("delete") 163 | public void deleteMovie(@PathParam("id") long id) { 164 | LOGGER.info("delete: " + toIdentityString()); 165 | service.deleteMovie(id); 166 | } 167 | 168 | @GET 169 | @Path("count") 170 | @Produces(MediaType.TEXT_PLAIN) 171 | public int count(@QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { 172 | LOGGER.info("count: " + toIdentityString()); 173 | return service.count(field, searchTerm); 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/rest/Person.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.rest; 18 | 19 | import org.eclipse.microprofile.jwt.Claim; 20 | import org.superbiz.moviefun.utils.DecryptedValue; 21 | 22 | import javax.enterprise.context.RequestScoped; 23 | import javax.inject.Inject; 24 | 25 | @RequestScoped 26 | public class Person { 27 | 28 | @Inject 29 | @Claim("username") 30 | private String username; 31 | 32 | @Inject 33 | @Claim("email") 34 | private String email; 35 | 36 | @Inject 37 | @Claim("language") 38 | private String language; 39 | 40 | @Inject 41 | @DecryptedValue("creditCard") 42 | private String creditCard; 43 | 44 | @Inject 45 | @Claim("preferredGenre") 46 | private String preferredGenre; 47 | 48 | public Person() { 49 | } 50 | 51 | public String getUsername() { 52 | return username; 53 | } 54 | 55 | public String getEmail() { 56 | return email; 57 | } 58 | 59 | public String getCreditCard() { 60 | return creditCard; 61 | } 62 | 63 | public String getPreferredGenre() { 64 | return preferredGenre; 65 | } 66 | 67 | public String getLanguage() { 68 | return language; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/sts/MovieClaimsSourceResource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.sts; 18 | 19 | import javax.ws.rs.Consumes; 20 | import javax.ws.rs.POST; 21 | import javax.ws.rs.Path; 22 | import javax.ws.rs.Produces; 23 | import javax.ws.rs.core.MediaType; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | @Path("claims") 28 | public class MovieClaimsSourceResource { 29 | 30 | private static HashMap data = new HashMap<>(); 31 | 32 | static { 33 | data.put("alex", new UserPreferences("SPANISH", "Guatemala JUG", "Mystery", "3211 1922 4433 1111")); 34 | data.put("john", new UserPreferences("ENGLISH", "Boston JUG", "Action", "2311 2345 8899 2222")); 35 | data.put("mark", new UserPreferences("ENGLISH", "London JUG", "Drama", "1122 6543 5858 3333")); 36 | data.put("nick", new UserPreferences("SPANISH", "Mexico JUG", "Comedy", "7789 8765 1222 4444")); 37 | } 38 | 39 | @POST 40 | @Produces(MediaType.APPLICATION_JSON) 41 | @Consumes(MediaType.APPLICATION_JSON) 42 | public UserPreferences authenticate(final Map payload) { 43 | String username = payload.get("username"); 44 | return data.get(username); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/sts/UserPreferences.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.sts; 18 | 19 | import org.superbiz.moviefun.utils.Cipher; 20 | 21 | import javax.xml.bind.annotation.XmlRootElement; 22 | 23 | @XmlRootElement(name = "preferences") 24 | public class UserPreferences { 25 | 26 | private String language; 27 | private String jug; 28 | private String creditCard; 29 | private String preferredGenre; 30 | 31 | public UserPreferences() { 32 | } 33 | 34 | public UserPreferences(final String language, final String jug, final String preferredGenre, final String creditCard) { 35 | this.language = language; 36 | this.jug = jug; 37 | this.creditCard = new String(Cipher.INSTANCE.getPasswordCipher().encrypt(creditCard)); 38 | this.preferredGenre = preferredGenre; 39 | } 40 | 41 | public String getLanguage() { 42 | return language; 43 | } 44 | 45 | public String getJug() { 46 | return jug; 47 | } 48 | 49 | public String getCreditCard() { 50 | return creditCard; 51 | } 52 | 53 | public String getPreferredGenre() { 54 | return preferredGenre; 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/utils/Cipher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.utils; 18 | 19 | import org.apache.openejb.cipher.PasswordCipher; 20 | import org.apache.openejb.cipher.StaticDESPasswordCipher; 21 | 22 | public enum Cipher { 23 | INSTANCE; 24 | 25 | final PasswordCipher passwordCipher = new StaticDESPasswordCipher(); 26 | 27 | public PasswordCipher getPasswordCipher() { 28 | return passwordCipher; 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/utils/CipherProduces.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.utils; 18 | 19 | import org.eclipse.microprofile.jwt.JsonWebToken; 20 | 21 | import javax.enterprise.context.ApplicationScoped; 22 | import javax.enterprise.context.RequestScoped; 23 | import javax.enterprise.inject.Produces; 24 | import javax.enterprise.inject.spi.InjectionPoint; 25 | import javax.inject.Inject; 26 | import java.util.Optional; 27 | 28 | @RequestScoped 29 | public class CipherProduces { 30 | 31 | @Inject 32 | private JsonWebToken jsonWebToken; 33 | 34 | @Produces 35 | @DecryptedValue 36 | public String decryptedCreditCard(InjectionPoint injectionPoint) { 37 | final DecryptedValue annotation = injectionPoint.getAnnotated().getAnnotation(DecryptedValue.class); 38 | final Optional claim = jsonWebToken.claim(annotation.value()); 39 | if (claim.isPresent()) { 40 | return Cipher.INSTANCE.getPasswordCipher().decrypt(claim.get().toString().toCharArray()); 41 | } 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/utils/DecryptedValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun.utils; 18 | 19 | import javax.enterprise.util.Nonbinding; 20 | import javax.inject.Qualifier; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.Target; 23 | 24 | import static java.lang.annotation.ElementType.FIELD; 25 | import static java.lang.annotation.ElementType.METHOD; 26 | import static java.lang.annotation.ElementType.PARAMETER; 27 | import static java.lang.annotation.ElementType.TYPE; 28 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 29 | 30 | @Qualifier 31 | @Retention(RUNTIME) 32 | @Target({TYPE, METHOD, FIELD, PARAMETER}) 33 | public @interface DecryptedValue { 34 | @Nonbinding 35 | String value() default ""; 36 | } -------------------------------------------------------------------------------- /src/main/java/org/superbiz/moviefun/utils/TokenUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2017 Contributors to the Eclipse Foundation 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information regarding copyright ownership. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * You may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | package org.superbiz.moviefun.utils; 21 | 22 | import com.nimbusds.jose.JOSEObjectType; 23 | import com.nimbusds.jose.JWSAlgorithm; 24 | import com.nimbusds.jose.JWSHeader; 25 | import com.nimbusds.jose.JWSSigner; 26 | import com.nimbusds.jose.crypto.MACSigner; 27 | import com.nimbusds.jose.crypto.RSASSASigner; 28 | import com.nimbusds.jwt.JWTClaimsSet; 29 | import com.nimbusds.jwt.SignedJWT; 30 | import net.minidev.json.JSONObject; 31 | import net.minidev.json.parser.JSONParser; 32 | import org.eclipse.microprofile.jwt.Claims; 33 | 34 | import java.io.InputStream; 35 | import java.math.BigInteger; 36 | import java.security.KeyFactory; 37 | import java.security.KeyPair; 38 | import java.security.KeyPairGenerator; 39 | import java.security.NoSuchAlgorithmException; 40 | import java.security.PrivateKey; 41 | import java.security.PublicKey; 42 | import java.security.SecureRandom; 43 | import java.security.spec.PKCS8EncodedKeySpec; 44 | import java.security.spec.X509EncodedKeySpec; 45 | import java.util.Base64; 46 | import java.util.Collections; 47 | import java.util.Map; 48 | import java.util.Set; 49 | 50 | /** 51 | * Utilities for generating a JWT for testing 52 | */ 53 | public class TokenUtil { 54 | 55 | public static final String PRIVATE_KEY_PEM = "/privateKey-pkcs8.pem"; 56 | 57 | private TokenUtil() { 58 | } 59 | 60 | /** 61 | * Utility method to generate a JWT string from a JSON resource file that is signed by the privateKey-pkcs1.pem 62 | * test resource key. 63 | * 64 | * @param jsonResName - name of test resources file 65 | * @return the JWT string 66 | * @throws Exception on parse failure 67 | */ 68 | public static String generateTokenString(String jsonResName) throws Exception { 69 | return generateTokenString(jsonResName, Collections.emptySet()); 70 | } 71 | 72 | /** 73 | * Utility method to generate a JWT string from a JSON resource file that is signed by the privateKey-pkcs1.pem 74 | * test resource key, possibly with invalid fields. 75 | * 76 | * @param jsonResName - name of test resources file 77 | * @param invalidClaims - the set of claims that should be added with invalid values to test failure modes 78 | * @return the JWT string 79 | * @throws Exception on parse failure 80 | */ 81 | public static String generateTokenString(String jsonResName, Set invalidClaims) throws Exception { 82 | return generateTokenString(jsonResName, invalidClaims, null); 83 | } 84 | 85 | 86 | public static JSONObject of(final String content) throws Exception { 87 | final JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE); 88 | return (JSONObject) parser.parse(content); 89 | } 90 | 91 | /** 92 | * Utility method to generate a JWT string from a JSON resource file that is signed by the privateKey-pkcs1.pem 93 | * test resource key, possibly with invalid fields. 94 | * 95 | * @param jsonResName - name of test resources file 96 | * @param invalidClaims - the set of claims that should be added with invalid values to test failure modes 97 | * @param timeClaims - used to return the exp, iat, auth_time claims 98 | * @return the JWT string 99 | * @throws Exception on parse failure 100 | */ 101 | public static String generateTokenString(String jsonResName, Set invalidClaims, Map timeClaims) throws Exception { 102 | if (invalidClaims == null) { 103 | invalidClaims = Collections.emptySet(); 104 | } 105 | final InputStream contentIS = TokenUtil.class.getResourceAsStream(jsonResName); 106 | byte[] tmp = new byte[4096]; 107 | int length = contentIS.read(tmp); 108 | byte[] content = new byte[length]; 109 | System.arraycopy(tmp, 0, content, 0, length); 110 | 111 | JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE); 112 | JSONObject jwtContent = (JSONObject) parser.parse(content); 113 | 114 | return generateTokenString(jwtContent, invalidClaims, timeClaims); 115 | } 116 | 117 | /** 118 | * Utility method to generate a JWT string from a JSON resource file that is signed by the privateKey-pkcs1.pem 119 | * test resource key, possibly with invalid fields. 120 | * 121 | * @param jwtContent - the JSON Payload for the JWT 122 | * @param invalidClaims - the set of claims that should be added with invalid values to test failure modes 123 | * @param timeClaims - used to return the exp, iat, auth_time claims 124 | * @return the JWT string 125 | * @throws Exception on parse failure 126 | */ 127 | public static String generateTokenString(JSONObject jwtContent, Set invalidClaims, Map timeClaims) throws Exception { 128 | if (invalidClaims == null) { 129 | invalidClaims = Collections.emptySet(); 130 | } 131 | 132 | // Change the issuer to INVALID_ISSUER for failure testing if requested 133 | if (invalidClaims.contains(InvalidClaims.ISSUER)) { 134 | jwtContent.put(Claims.iss.name(), "INVALID_ISSUER"); 135 | } 136 | 137 | long currentTimeInSecs = currentTimeInSecs(); 138 | long exp = currentTimeInSecs + 300; 139 | 140 | // Check for an input exp to override the default of now + 300 seconds 141 | if (timeClaims != null && timeClaims.containsKey(Claims.exp.name())) { 142 | exp = timeClaims.get(Claims.exp.name()); 143 | } 144 | jwtContent.put(Claims.iat.name(), currentTimeInSecs); 145 | jwtContent.put(Claims.auth_time.name(), currentTimeInSecs); 146 | 147 | // If the exp claim is not updated, it will be an old value that should be seen as expired 148 | if (!invalidClaims.contains(InvalidClaims.EXP)) { 149 | jwtContent.put(Claims.exp.name(), exp); 150 | } 151 | if (timeClaims != null) { 152 | timeClaims.put(Claims.iat.name(), currentTimeInSecs); 153 | timeClaims.put(Claims.auth_time.name(), currentTimeInSecs); 154 | timeClaims.put(Claims.exp.name(), exp); 155 | } 156 | 157 | PrivateKey pk; 158 | if (invalidClaims.contains(InvalidClaims.SIGNER)) { 159 | // Generate a new random private key to sign with to test invalid signatures 160 | KeyPair keyPair = generateKeyPair(2048); 161 | pk = keyPair.getPrivate(); 162 | 163 | } else { 164 | 165 | // Use the test private key associated with the test public key for a valid signature 166 | pk = readPrivateKey(PRIVATE_KEY_PEM); 167 | } 168 | 169 | // Create RSA-signer with the private key 170 | JWSSigner signer = new RSASSASigner(pk); 171 | final JWTClaimsSet claimsSet = JWTClaimsSet.parse(jwtContent); 172 | JWSAlgorithm alg = JWSAlgorithm.RS256; 173 | 174 | if (invalidClaims.contains(InvalidClaims.ALG)) { 175 | alg = JWSAlgorithm.HS256; 176 | final SecureRandom random = new SecureRandom(); 177 | final BigInteger secret = BigInteger.probablePrime(256, random); 178 | signer = new MACSigner(secret.toByteArray()); 179 | } 180 | 181 | final JWSHeader jwtHeader = new JWSHeader.Builder(alg) 182 | .keyID(PRIVATE_KEY_PEM) 183 | .type(JOSEObjectType.JWT) 184 | .build(); 185 | 186 | final SignedJWT signedJWT = new SignedJWT(jwtHeader, claimsSet); 187 | signedJWT.sign(signer); 188 | return signedJWT.serialize(); 189 | } 190 | 191 | /** 192 | * Read a PEM encoded private key from the classpath 193 | * 194 | * @param pemResName - key file resource name 195 | * @return PrivateKey 196 | * @throws Exception on decode failure 197 | */ 198 | public static PrivateKey readPrivateKey(String pemResName) throws Exception { 199 | InputStream contentIS = TokenUtil.class.getResourceAsStream(pemResName); 200 | byte[] tmp = new byte[4096]; 201 | int length = contentIS.read(tmp); 202 | return decodePrivateKey(new String(tmp, 0, length)); 203 | } 204 | 205 | /** 206 | * Read a PEM encoded public key from the classpath 207 | * 208 | * @param pemResName - key file resource name 209 | * @return PublicKey 210 | * @throws Exception on decode failure 211 | */ 212 | public static PublicKey readPublicKey(String pemResName) throws Exception { 213 | InputStream contentIS = TokenUtil.class.getResourceAsStream(pemResName); 214 | byte[] tmp = new byte[4096]; 215 | int length = contentIS.read(tmp); 216 | return decodePublicKey(new String(tmp, 0, length)); 217 | } 218 | 219 | /** 220 | * Generate a new RSA keypair. 221 | * 222 | * @param keySize - the size of the key 223 | * @return KeyPair 224 | * @throws NoSuchAlgorithmException on failure to load RSA key generator 225 | */ 226 | public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException { 227 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); 228 | keyPairGenerator.initialize(keySize); 229 | return keyPairGenerator.genKeyPair(); 230 | } 231 | 232 | /** 233 | * Decode a PEM encoded private key string to an RSA PrivateKey 234 | * 235 | * @param pemEncoded - PEM string for private key 236 | * @return PrivateKey 237 | * @throws Exception on decode failure 238 | */ 239 | public static PrivateKey decodePrivateKey(String pemEncoded) throws Exception { 240 | pemEncoded = removeBeginEnd(pemEncoded); 241 | byte[] pkcs8EncodedBytes = Base64.getDecoder().decode(pemEncoded); 242 | 243 | // extract the private key 244 | 245 | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedBytes); 246 | KeyFactory kf = KeyFactory.getInstance("RSA"); 247 | return kf.generatePrivate(keySpec); 248 | } 249 | 250 | /** 251 | * Decode a PEM encoded public key string to an RSA PublicKey 252 | * 253 | * @param pemEncoded - PEM string for private key 254 | * @return PublicKey 255 | * @throws Exception on decode failure 256 | */ 257 | public static PublicKey decodePublicKey(String pemEncoded) throws Exception { 258 | pemEncoded = removeBeginEnd(pemEncoded); 259 | byte[] encodedBytes = Base64.getDecoder().decode(pemEncoded); 260 | 261 | X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes); 262 | KeyFactory kf = KeyFactory.getInstance("RSA"); 263 | return kf.generatePublic(spec); 264 | } 265 | 266 | private static String removeBeginEnd(String pem) { 267 | pem = pem.replaceAll("-----BEGIN (.*)-----", ""); 268 | pem = pem.replaceAll("-----END (.*)----", ""); 269 | pem = pem.replaceAll("\r\n", ""); 270 | pem = pem.replaceAll("\n", ""); 271 | return pem.trim(); 272 | } 273 | 274 | /** 275 | * @return the current time in seconds since epoch 276 | */ 277 | public static int currentTimeInSecs() { 278 | long currentTimeMS = System.currentTimeMillis(); 279 | return (int) (currentTimeMS / 1000); 280 | } 281 | 282 | /** 283 | * Enums to indicate which claims should be set to invalid values for testing failure modes 284 | */ 285 | public enum InvalidClaims { 286 | ISSUER, // Set an invalid issuer 287 | EXP, // Set an invalid expiration 288 | SIGNER, // Sign the token with the incorrect private key 289 | ALG, // Sign the token with the correct private key, but HS 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/main/java/org/superbiz/rest/GreetingResource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.rest; 18 | 19 | import javax.ws.rs.Consumes; 20 | import javax.ws.rs.GET; 21 | import javax.ws.rs.POST; 22 | import javax.ws.rs.Path; 23 | import javax.ws.rs.Produces; 24 | import javax.ws.rs.core.MediaType; 25 | import java.util.Locale; 26 | 27 | @Path("/greeting") 28 | @Produces(MediaType.APPLICATION_JSON) 29 | @Consumes(MediaType.APPLICATION_JSON) 30 | public class GreetingResource { 31 | 32 | @GET 33 | public String message() { 34 | return "Hi Microprofile JWT!"; 35 | } 36 | 37 | @POST 38 | public String lowerCase(final String message) { 39 | return message.toLowerCase(Locale.ENGLISH); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 22 | 23 | movieDatabase 24 | movieDatabaseUnmanaged 25 | org.superbiz.moviefun.Movie 26 | org.superbiz.moviefun.Comment 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/resources/account-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "testtest", 3 | "email": "test@example.com", 4 | "displayName": "Tester one", 5 | "roles": [ 6 | { 7 | "displayName": "delete", 8 | "name": "delete", 9 | "id": "delete" 10 | }, 11 | { 12 | "displayName": "update", 13 | "name": "update", 14 | "id": "update" 15 | }, 16 | { 17 | "displayName": "create", 18 | "name": "create", 19 | "id": "create" 20 | } 21 | ], 22 | "credentials": { 23 | "clientSecret": { 24 | "oAuth2ProfileRef": "movies", 25 | "active": true, 26 | "value": "testtest" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/privateKey-pkcs1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAkspegJhNClHF7hdBP41zyYoQrR0L0m2JA/0Ctj2A4o2sJnNn 3 | fxM6vi1HmhJUOERp7KRkUHWAqcW/aGYpdv/hIlS/vUQ+ZTnsZWlykiqj8eKZTY9H 4 | U6pZpi+Sa3VC60Ar/nHWHVqbJcKMkoNZgrIcLr14jQrr4ZSYlaZEJUgCtAm7bHlX 5 | l1PEHdxtQ3CY6gWkW2ynQXjq7wPxrhTbJHlRmaQmEczJa8VK+M2/K7k3Ys44UsfT 6 | mS0c6lU1EfhvwCq38zQLjw2CQuVb9gnLvrA4CpeTS7XAeUOoa/n1AVPpK+pv0B1Z 7 | tJXQu5u+boESjMDuk2QidTZzBymqedS9hxWCWwIDAQABAoIBAEYJYj+O6ysiSwLH 8 | e60762PciQpf0nUrJ/WMMVAMVkNB/0I1S8s4vI1ig0hCuIZENhnfcbFl7uaR4DqK 9 | i/woKB2+O+Gs/uxDT8QvJKgSyjgtuqFj3E9R9wYwqna08yHVc2gqnlNRGLdSdMmu 10 | +/U8z++JHUyGSndN8+Nq+hajng6R4KsTre80IgK98En4jymgQuOhfzeThECc5LHN 11 | wImetrwoOkuPaF0tyuiEyQsj2PIwctIQ5X5O2NQu/Y8u3Eip6DcFnWdMxZxNH8nH 12 | f97bR7s46d2R49BnmIlw//pQ82BXSdQaMwPNjcJHpbWAUaLxWP/1GKce1pvmUZAg 13 | 4Wh7sMECgYEA0LNbMIAqmznjX8u1W/B5W5h4gFuIKPNAMy020P2CqFlOePOKLgJL 14 | pI7yukVdmEjgHDGfNqtj+cNy7LHeEoemZUKmSLBcHCU/liki7t3ab4Kyd12FZgPd 15 | HmbHWQbqEsu1jC3y/oA3g71kz2yko3t75xZ6hLZKkPr75zTTWS8BhmECgYEAtA8J 16 | qRXAhx/JpCzWd5WMWHLhqKEQslyPADcESWClsEbeQ8s87yCw+BOVTVKmBkHpjLR+ 17 | nwwwg4NM7dE8axfuVF5bgXsYrcz/1l0QkgNiEkJQ/SB7173/7GlgvV7WBfK9nG+Y 18 | D7hfCopS7u6v1G/hBnZJ2YJf1ofT9J4E8NSTyjsCgYBdjNhGgF0Y4bUDXuv3v26W 19 | 2VzCpMT/HjLb/duBiPHFhuq/GuQIxaykohM53hgbSCd1W+Tze5ZAAhWE9iIGilnT 20 | MDIbiTpwv12mcOAg1L+ylpJrITfHx9mZZBbd2FSagkfqAzrWTCEWY5JJzHhsc9DR 21 | gGkBDjmUjXzXYf2PD5wOYQKBgGwsDJW0J7IF+tHSzhWRlnscqUzxVmKREKgEZWLf 22 | 2SqJqMX5t2XBsg+XVD7bxDpGJtUNKnTKkeqwWusUpMOB4QB2n2quVSk02w4hYu8V 23 | cTme9aDcfwohbzrMI/4gl1uDdT4iHKx1C0P9zc0VQDTT8dA8CCnQFVuAxmlS9Yzp 24 | aNA5AoGATbUOFIQj8cQR3fTGGEpGKGJ8brm5iHnpULUdadhEZOfS44UAtTrcv6zO 25 | 9BaabZLZh0TkUo0kkFLwuucDWhf4XC7W0s39IcaowbkCG5uq56rxcRRdahP2nY0c 26 | +X9nTdWCCdUYHx1wmg1/F//09iwq/TlQfJYja6xsoOGNI5jvMmE= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/main/resources/privateKey-pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCSyl6AmE0KUcXu 3 | F0E/jXPJihCtHQvSbYkD/QK2PYDijawmc2d/Ezq+LUeaElQ4RGnspGRQdYCpxb9o 4 | Zil2/+EiVL+9RD5lOexlaXKSKqPx4plNj0dTqlmmL5JrdULrQCv+cdYdWpslwoyS 5 | g1mCshwuvXiNCuvhlJiVpkQlSAK0CbtseVeXU8Qd3G1DcJjqBaRbbKdBeOrvA/Gu 6 | FNskeVGZpCYRzMlrxUr4zb8ruTdizjhSx9OZLRzqVTUR+G/AKrfzNAuPDYJC5Vv2 7 | Ccu+sDgKl5NLtcB5Q6hr+fUBU+kr6m/QHVm0ldC7m75ugRKMwO6TZCJ1NnMHKap5 8 | 1L2HFYJbAgMBAAECggEARgliP47rKyJLAsd7rTvrY9yJCl/SdSsn9YwxUAxWQ0H/ 9 | QjVLyzi8jWKDSEK4hkQ2Gd9xsWXu5pHgOoqL/CgoHb474az+7ENPxC8kqBLKOC26 10 | oWPcT1H3BjCqdrTzIdVzaCqeU1EYt1J0ya779TzP74kdTIZKd03z42r6FqOeDpHg 11 | qxOt7zQiAr3wSfiPKaBC46F/N5OEQJzksc3AiZ62vCg6S49oXS3K6ITJCyPY8jBy 12 | 0hDlfk7Y1C79jy7cSKnoNwWdZ0zFnE0fycd/3ttHuzjp3ZHj0GeYiXD/+lDzYFdJ 13 | 1BozA82NwkeltYBRovFY//UYpx7Wm+ZRkCDhaHuwwQKBgQDQs1swgCqbOeNfy7Vb 14 | 8HlbmHiAW4go80AzLTbQ/YKoWU5484ouAkukjvK6RV2YSOAcMZ82q2P5w3Lssd4S 15 | h6ZlQqZIsFwcJT+WKSLu3dpvgrJ3XYVmA90eZsdZBuoSy7WMLfL+gDeDvWTPbKSj 16 | e3vnFnqEtkqQ+vvnNNNZLwGGYQKBgQC0DwmpFcCHH8mkLNZ3lYxYcuGooRCyXI8A 17 | NwRJYKWwRt5DyzzvILD4E5VNUqYGQemMtH6fDDCDg0zt0TxrF+5UXluBexitzP/W 18 | XRCSA2ISQlD9IHvXvf/saWC9XtYF8r2cb5gPuF8KilLu7q/Ub+EGdknZgl/Wh9P0 19 | ngTw1JPKOwKBgF2M2EaAXRjhtQNe6/e/bpbZXMKkxP8eMtv924GI8cWG6r8a5AjF 20 | rKSiEzneGBtIJ3Vb5PN7lkACFYT2IgaKWdMwMhuJOnC/XaZw4CDUv7KWkmshN8fH 21 | 2ZlkFt3YVJqCR+oDOtZMIRZjkknMeGxz0NGAaQEOOZSNfNdh/Y8PnA5hAoGAbCwM 22 | lbQnsgX60dLOFZGWexypTPFWYpEQqARlYt/ZKomoxfm3ZcGyD5dUPtvEOkYm1Q0q 23 | dMqR6rBa6xSkw4HhAHafaq5VKTTbDiFi7xVxOZ71oNx/CiFvOswj/iCXW4N1PiIc 24 | rHULQ/3NzRVANNPx0DwIKdAVW4DGaVL1jOlo0DkCgYBNtQ4UhCPxxBHd9MYYSkYo 25 | YnxuubmIeelQtR1p2ERk59LjhQC1Oty/rM70FpptktmHRORSjSSQUvC65wNaF/hc 26 | LtbSzf0hxqjBuQIbm6rnqvFxFF1qE/adjRz5f2dN1YIJ1RgfHXCaDX8X//T2LCr9 27 | OVB8liNrrGyg4Y0jmO8yYQ== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /src/main/resources/profile-oauth2.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefix": "Bearer", 3 | "header": "Authorization", 4 | "name": "movies", 5 | "refreshAllowed": true, 6 | "requiresClient": false, 7 | "accessTokenExpireTTL": "5 minutes", 8 | "refreshTokenExpireTTL": "1 hour", 9 | "allowedGrants": [ 10 | "password", 11 | "client_credentials", 12 | "refresh_token" 13 | ], 14 | "claims": [ 15 | { 16 | "claimSourceType": "ACCOUNT", 17 | "source": "name", 18 | "type": "PRINCIPAL", 19 | "target": "username" 20 | }, 21 | { 22 | "claimSourceType": "ACCOUNT", 23 | "source": "email", 24 | "type": "CLAIM", 25 | "target": "email" 26 | }, 27 | { 28 | "claimSourceType": "ACCOUNT", 29 | "source": "roles", 30 | "type": "ROLE", 31 | "target": "groups" 32 | }, 33 | { 34 | "claimSourceType": "ACCOUNT", 35 | "source": "name", 36 | "type": "CLAIM", 37 | "target": "sub" 38 | }, 39 | { 40 | "claimSourceType": "HTTP", 41 | "claimSourceRef": "movies-claims-source", 42 | "source": "preferredGenre", 43 | "type": "CLAIM", 44 | "target": "preferredGenre" 45 | }, 46 | { 47 | "claimSourceType": "HTTP", 48 | "claimSourceRef": "movies-claims-source", 49 | "source": "jug", 50 | "type": "CLAIM", 51 | "target": "jug" 52 | }, 53 | { 54 | "claimSourceType": "HTTP", 55 | "claimSourceRef": "movies-claims-source", 56 | "source": "creditCard", 57 | "type": "CLAIM", 58 | "target": "creditCard" 59 | }, 60 | { 61 | "claimSourceType": "HTTP", 62 | "claimSourceRef": "movies-claims-source", 63 | "source": "language", 64 | "type": "CLAIM", 65 | "target": "language" 66 | } 67 | ], 68 | "externalSigning": { 69 | "signingAlgorithm": "RS256", 70 | "publicPrivateKeys": [ 71 | { 72 | "type": "RSA", 73 | "algorithm": "RSA-SHA256", 74 | "id": "my-rsa-key", 75 | "privateValue": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAkspegJhNClHF7hdBP41zyYoQrR0L0m2JA/0Ctj2A4o2sJnNn\nfxM6vi1HmhJUOERp7KRkUHWAqcW/aGYpdv/hIlS/vUQ+ZTnsZWlykiqj8eKZTY9H\nU6pZpi+Sa3VC60Ar/nHWHVqbJcKMkoNZgrIcLr14jQrr4ZSYlaZEJUgCtAm7bHlX\nl1PEHdxtQ3CY6gWkW2ynQXjq7wPxrhTbJHlRmaQmEczJa8VK+M2/K7k3Ys44UsfT\nmS0c6lU1EfhvwCq38zQLjw2CQuVb9gnLvrA4CpeTS7XAeUOoa/n1AVPpK+pv0B1Z\ntJXQu5u+boESjMDuk2QidTZzBymqedS9hxWCWwIDAQABAoIBAEYJYj+O6ysiSwLH\ne60762PciQpf0nUrJ/WMMVAMVkNB/0I1S8s4vI1ig0hCuIZENhnfcbFl7uaR4DqK\ni/woKB2+O+Gs/uxDT8QvJKgSyjgtuqFj3E9R9wYwqna08yHVc2gqnlNRGLdSdMmu\n+/U8z++JHUyGSndN8+Nq+hajng6R4KsTre80IgK98En4jymgQuOhfzeThECc5LHN\nwImetrwoOkuPaF0tyuiEyQsj2PIwctIQ5X5O2NQu/Y8u3Eip6DcFnWdMxZxNH8nH\nf97bR7s46d2R49BnmIlw//pQ82BXSdQaMwPNjcJHpbWAUaLxWP/1GKce1pvmUZAg\n4Wh7sMECgYEA0LNbMIAqmznjX8u1W/B5W5h4gFuIKPNAMy020P2CqFlOePOKLgJL\npI7yukVdmEjgHDGfNqtj+cNy7LHeEoemZUKmSLBcHCU/liki7t3ab4Kyd12FZgPd\nHmbHWQbqEsu1jC3y/oA3g71kz2yko3t75xZ6hLZKkPr75zTTWS8BhmECgYEAtA8J\nqRXAhx/JpCzWd5WMWHLhqKEQslyPADcESWClsEbeQ8s87yCw+BOVTVKmBkHpjLR+\nnwwwg4NM7dE8axfuVF5bgXsYrcz/1l0QkgNiEkJQ/SB7173/7GlgvV7WBfK9nG+Y\nD7hfCopS7u6v1G/hBnZJ2YJf1ofT9J4E8NSTyjsCgYBdjNhGgF0Y4bUDXuv3v26W\n2VzCpMT/HjLb/duBiPHFhuq/GuQIxaykohM53hgbSCd1W+Tze5ZAAhWE9iIGilnT\nMDIbiTpwv12mcOAg1L+ylpJrITfHx9mZZBbd2FSagkfqAzrWTCEWY5JJzHhsc9DR\ngGkBDjmUjXzXYf2PD5wOYQKBgGwsDJW0J7IF+tHSzhWRlnscqUzxVmKREKgEZWLf\n2SqJqMX5t2XBsg+XVD7bxDpGJtUNKnTKkeqwWusUpMOB4QB2n2quVSk02w4hYu8V\ncTme9aDcfwohbzrMI/4gl1uDdT4iHKx1C0P9zc0VQDTT8dA8CCnQFVuAxmlS9Yzp\naNA5AoGATbUOFIQj8cQR3fTGGEpGKGJ8brm5iHnpULUdadhEZOfS44UAtTrcv6zO\n9BaabZLZh0TkUo0kkFLwuucDWhf4XC7W0s39IcaowbkCG5uq56rxcRRdahP2nY0c\n+X9nTdWCCdUYHx1wmg1/F//09iwq/TlQfJYja6xsoOGNI5jvMmE=\n-----END RSA PRIVATE KEY-----\n", 76 | "active": true 77 | } 78 | ] 79 | }, 80 | "httpSignatureProfile": { 81 | "id": "http-signatures-profile" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/resources/publicKey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkspegJhNClHF7hdBP41z 3 | yYoQrR0L0m2JA/0Ctj2A4o2sJnNnfxM6vi1HmhJUOERp7KRkUHWAqcW/aGYpdv/h 4 | IlS/vUQ+ZTnsZWlykiqj8eKZTY9HU6pZpi+Sa3VC60Ar/nHWHVqbJcKMkoNZgrIc 5 | Lr14jQrr4ZSYlaZEJUgCtAm7bHlXl1PEHdxtQ3CY6gWkW2ynQXjq7wPxrhTbJHlR 6 | maQmEczJa8VK+M2/K7k3Ys44UsfTmS0c6lU1EfhvwCq38zQLjw2CQuVb9gnLvrA4 7 | CpeTS7XAeUOoa/n1AVPpK+pv0B1ZtJXQu5u+boESjMDuk2QidTZzBymqedS9hxWC 8 | WwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /src/main/resources/route.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "movies", 3 | "modRewrite": "RewriteCond %{REQUEST_METHOD} POST [OR]\nRewriteCond %{REQUEST_METHOD} PUT [OR]\nRewriteCond %{REQUEST_METHOD} DELETE\nRewriteRule \"^/moviefun(.*)$\" \"http://localhost:8181/moviefun$1\" [P,auth]\n\nRewriteRule \"^/moviefun(.*)$\" \"http://localhost:8181/moviefun$1\" [P]", 4 | "profileRequiresAll": true, 5 | "profiles": [ 6 | { 7 | "id": "movies", 8 | "name": "movies", 9 | "displayName": "movies" 10 | } 11 | ], 12 | "index": 5, 13 | "roleRequiresAll": true 14 | } 15 | -------------------------------------------------------------------------------- /src/main/tag/import/account-alex.json: -------------------------------------------------------------------------------- 1 | //api/account/ 2 | { 3 | "username": "alex", 4 | "email": "alex@superbiz.com", 5 | "displayName": "Alex the Rocker", 6 | "roles": [ 7 | { 8 | "displayName": "delete", 9 | "name": "delete", 10 | "id": "delete" 11 | }, 12 | { 13 | "displayName": "update", 14 | "name": "update", 15 | "id": "update" 16 | }, 17 | { 18 | "displayName": "create", 19 | "name": "create", 20 | "id": "create" 21 | } 22 | ], 23 | "credentials": { 24 | "password": { 25 | "active": true, 26 | "value": "password", 27 | "createdDate": "2018-04-29T20:22:01Z" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/tag/import/account-john.json: -------------------------------------------------------------------------------- 1 | //api/account/ 2 | { 3 | "username": "john", 4 | "email": "john@superbiz.com", 5 | "displayName": "John Lenon", 6 | "roles": [ 7 | { 8 | "displayName": "create", 9 | "name": "create", 10 | "id": "create" 11 | } 12 | ], 13 | "credentials": { 14 | "password": { 15 | "active": true, 16 | "value": "password", 17 | "createdDate": "2018-04-29T20:22:01Z" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/tag/import/account-mark.json: -------------------------------------------------------------------------------- 1 | //api/account/ 2 | { 3 | "username": "mark", 4 | "email": "mark@superbiz.com", 5 | "displayName": "Mark Simon", 6 | "roles": [ 7 | { 8 | "displayName": "update", 9 | "name": "update", 10 | "id": "update" 11 | } 12 | ], 13 | "credentials": { 14 | "password": { 15 | "active": true, 16 | "value": "password", 17 | "createdDate": "2018-04-29T20:22:01Z" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/tag/import/account-nick.json: -------------------------------------------------------------------------------- 1 | //api/account/ 2 | { 3 | "username": "nick", 4 | "email": "nick@superbiz.com", 5 | "displayName": "Nick Olson", 6 | "roles": [ 7 | { 8 | "displayName": "delete", 9 | "name": "delete", 10 | "id": "delete" 11 | } 12 | ], 13 | "credentials": { 14 | "password": { 15 | "active": true, 16 | "value": "password", 17 | "createdDate": "2018-04-29T20:22:01Z" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/tag/import/account-test.json: -------------------------------------------------------------------------------- 1 | //api/account/ 2 | { 3 | "username": "testtest", 4 | "email": "test@example.com", 5 | "displayName": "Tester one", 6 | "roles": [ 7 | { 8 | "displayName": "app-config", 9 | "name": "app-config", 10 | "id": "app-config" 11 | } 12 | ], 13 | "credentials": { 14 | "clientSecret": { 15 | "oAuth2ProfileRef": "movies", 16 | "active": true, 17 | "value": "testtest" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/tag/import/api-claims-movies.json: -------------------------------------------------------------------------------- 1 | //api/claim/source/ 2 | { 3 | "type": "HTTP", 4 | "name": "Movies claims source", 5 | "configuration": { 6 | "path": "/moviefun/rest/claims" 7 | }, 8 | "connectionRef": { 9 | "id": "movies-api-connection" 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/tag/import/api-movies.json: -------------------------------------------------------------------------------- 1 | //api/http/ 2 | { 3 | "displayName": null, 4 | "description": null, 5 | "tags": [], 6 | "pool": { 7 | "enabledProtocols": [ 8 | "ALL" 9 | ], 10 | "enabledCipherSuites": [ 11 | "ALL" 12 | ] 13 | }, 14 | "name": "Movies API Connection", 15 | "endpoint": "http://localhost:8181" 16 | } -------------------------------------------------------------------------------- /src/main/tag/import/default-keysize-2048.json: -------------------------------------------------------------------------------- 1 | //api/settings/bulk/write 2 | { 3 | "settings": [ 4 | { 5 | "key": "com.tomitribe.tribestream.container.service.Policies.keyPairDefaultSize", 6 | "value": "2048" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /src/main/tag/import/default-oauth2-profile.json: -------------------------------------------------------------------------------- 1 | //api/settings/bulk/write 2 | { 3 | "settings": [ 4 | { 5 | "key": "com.tomitribe.tribestream.container.oauth2.OAuth2Configuration.defaultProfile", 6 | "value": "movies" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /src/main/tag/import/movies-admin.json: -------------------------------------------------------------------------------- 1 | //api/account/ 2 | { 3 | "credentials": { 4 | "publicPrivateKeys": [], 5 | "secretKeys": [ 6 | { 7 | "active": true, 8 | "id": "my-key-id", 9 | "type": "HMAC", 10 | "value": "YmQwYzE4MTg4ZjJjMWVkNWJhOTE3Yzc5MTRlYzI1ZjMxYTZiZDdlMDYxZWRjMDgx", 11 | "spec": "hmac-sha256" 12 | } 13 | ] 14 | }, 15 | "name": "Movies admin", 16 | "roles": [ 17 | { 18 | "displayName": "movies-api-connection", 19 | "name": "movies-api-connection", 20 | "id": "movies-api-connection" 21 | }, 22 | { 23 | "displayName": "Permission to administrate this Movies API Connection", 24 | "name": "api_api-connection.movies-api-connection", 25 | "id": "api_api-connection.movies-api-connection" 26 | } 27 | ], 28 | "groups": [], 29 | "type": "CLIENT", 30 | "tags": [], 31 | "sourceType": "ACCOUNT", 32 | "id": "movies-admin", 33 | "sourceRef": { 34 | "id": "gateway-accounts" 35 | }, 36 | "email": "admin@superbiz.io", 37 | "username": "movies-admin" 38 | } -------------------------------------------------------------------------------- /src/main/tag/import/profile-oauth2-movies.json: -------------------------------------------------------------------------------- 1 | //api/profile/oauth2 2 | { 3 | "prefix": "Bearer", 4 | "header": "Authorization", 5 | "name": "movies", 6 | "refreshAllowed": true, 7 | "requiresClient": false, 8 | "accessTokenExpireTTL": "5 minutes", 9 | "refreshTokenExpireTTL": "1 hour", 10 | "allowedGrants": [ 11 | "password", 12 | "client_credentials", 13 | "refresh_token" 14 | ], 15 | "claims": [ 16 | { 17 | "claimSourceType": "ACCOUNT", 18 | "source": "name", 19 | "type": "PRINCIPAL", 20 | "target": "username" 21 | }, 22 | { 23 | "claimSourceType": "ACCOUNT", 24 | "source": "email", 25 | "type": "CLAIM", 26 | "target": "email" 27 | }, 28 | { 29 | "claimSourceType": "ACCOUNT", 30 | "source": "roles", 31 | "type": "ROLE", 32 | "target": "groups" 33 | }, 34 | { 35 | "claimSourceType": "ACCOUNT", 36 | "source": "name", 37 | "type": "CLAIM", 38 | "target": "sub" 39 | }, 40 | { 41 | "claimSourceType": "HTTP", 42 | "claimSourceRef": "movies-claims-source", 43 | "source": "preferredGenre", 44 | "type": "CLAIM", 45 | "target": "preferredGenre" 46 | }, 47 | { 48 | "claimSourceType": "HTTP", 49 | "claimSourceRef": "movies-claims-source", 50 | "source": "jug", 51 | "type": "CLAIM", 52 | "target": "jug" 53 | }, 54 | { 55 | "claimSourceType": "HTTP", 56 | "claimSourceRef": "movies-claims-source", 57 | "source": "creditCard", 58 | "type": "CLAIM", 59 | "target": "creditCard" 60 | }, 61 | { 62 | "claimSourceType": "HTTP", 63 | "claimSourceRef": "movies-claims-source", 64 | "source": "language", 65 | "type": "CLAIM", 66 | "target": "language" 67 | } 68 | ], 69 | "externalSigning": { 70 | "signingAlgorithm": "RS256", 71 | "publicPrivateKeys": [ 72 | { 73 | "type": "RSA", 74 | "algorithm": "RSA-SHA256", 75 | "id": "my-rsa-key", 76 | "privateValue": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAkspegJhNClHF7hdBP41zyYoQrR0L0m2JA/0Ctj2A4o2sJnNn\nfxM6vi1HmhJUOERp7KRkUHWAqcW/aGYpdv/hIlS/vUQ+ZTnsZWlykiqj8eKZTY9H\nU6pZpi+Sa3VC60Ar/nHWHVqbJcKMkoNZgrIcLr14jQrr4ZSYlaZEJUgCtAm7bHlX\nl1PEHdxtQ3CY6gWkW2ynQXjq7wPxrhTbJHlRmaQmEczJa8VK+M2/K7k3Ys44UsfT\nmS0c6lU1EfhvwCq38zQLjw2CQuVb9gnLvrA4CpeTS7XAeUOoa/n1AVPpK+pv0B1Z\ntJXQu5u+boESjMDuk2QidTZzBymqedS9hxWCWwIDAQABAoIBAEYJYj+O6ysiSwLH\ne60762PciQpf0nUrJ/WMMVAMVkNB/0I1S8s4vI1ig0hCuIZENhnfcbFl7uaR4DqK\ni/woKB2+O+Gs/uxDT8QvJKgSyjgtuqFj3E9R9wYwqna08yHVc2gqnlNRGLdSdMmu\n+/U8z++JHUyGSndN8+Nq+hajng6R4KsTre80IgK98En4jymgQuOhfzeThECc5LHN\nwImetrwoOkuPaF0tyuiEyQsj2PIwctIQ5X5O2NQu/Y8u3Eip6DcFnWdMxZxNH8nH\nf97bR7s46d2R49BnmIlw//pQ82BXSdQaMwPNjcJHpbWAUaLxWP/1GKce1pvmUZAg\n4Wh7sMECgYEA0LNbMIAqmznjX8u1W/B5W5h4gFuIKPNAMy020P2CqFlOePOKLgJL\npI7yukVdmEjgHDGfNqtj+cNy7LHeEoemZUKmSLBcHCU/liki7t3ab4Kyd12FZgPd\nHmbHWQbqEsu1jC3y/oA3g71kz2yko3t75xZ6hLZKkPr75zTTWS8BhmECgYEAtA8J\nqRXAhx/JpCzWd5WMWHLhqKEQslyPADcESWClsEbeQ8s87yCw+BOVTVKmBkHpjLR+\nnwwwg4NM7dE8axfuVF5bgXsYrcz/1l0QkgNiEkJQ/SB7173/7GlgvV7WBfK9nG+Y\nD7hfCopS7u6v1G/hBnZJ2YJf1ofT9J4E8NSTyjsCgYBdjNhGgF0Y4bUDXuv3v26W\n2VzCpMT/HjLb/duBiPHFhuq/GuQIxaykohM53hgbSCd1W+Tze5ZAAhWE9iIGilnT\nMDIbiTpwv12mcOAg1L+ylpJrITfHx9mZZBbd2FSagkfqAzrWTCEWY5JJzHhsc9DR\ngGkBDjmUjXzXYf2PD5wOYQKBgGwsDJW0J7IF+tHSzhWRlnscqUzxVmKREKgEZWLf\n2SqJqMX5t2XBsg+XVD7bxDpGJtUNKnTKkeqwWusUpMOB4QB2n2quVSk02w4hYu8V\ncTme9aDcfwohbzrMI/4gl1uDdT4iHKx1C0P9zc0VQDTT8dA8CCnQFVuAxmlS9Yzp\naNA5AoGATbUOFIQj8cQR3fTGGEpGKGJ8brm5iHnpULUdadhEZOfS44UAtTrcv6zO\n9BaabZLZh0TkUo0kkFLwuucDWhf4XC7W0s39IcaowbkCG5uq56rxcRRdahP2nY0c\n+X9nTdWCCdUYHx1wmg1/F//09iwq/TlQfJYja6xsoOGNI5jvMmE=\n-----END RSA PRIVATE KEY-----\n", 77 | "active": true 78 | } 79 | ] 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/tag/import/route-movies.json: -------------------------------------------------------------------------------- 1 | //api/route 2 | { 3 | "name": "movies", 4 | "modRewrite": "RewriteCond %{REQUEST_METHOD} POST [OR]\nRewriteCond %{REQUEST_METHOD} PUT [OR]\nRewriteCond %{REQUEST_METHOD} DELETE\nRewriteRule \"^/movies(.*)$\" \"%{API:Movies API Connection}/moviefun$1\" [P,auth]\n\nRewriteRule \"^/movies(.*)$\" \"%{API:Movies API Connection}/moviefun$1\" [P]", 5 | "profileRequiresAll": true, 6 | "profiles": [ 7 | { 8 | "id": "movies", 9 | "name": "movies" 10 | } 11 | ], 12 | "index": 5, 13 | "roleRequiresAll": true 14 | } -------------------------------------------------------------------------------- /src/main/tomee-backup/resources.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | hostUrl = http://localhost:8080 23 | # acceptAllCertificates = true 24 | 25 | # do we need to unregister at the end. Worse case, the TAG will figure out it's offline and yank it from the hosts list 26 | # unregisterOnShutdown = 27 | # unregisterEndpoint = 28 | 29 | # URL where we gonna register 30 | # registerEndpoint = 31 | 32 | # host details 33 | # active = true 34 | # weight = 1 35 | serverUrl = http://localhost:8182 36 | connectionId = movies-api-connection 37 | 38 | # HTTP Signatures credentials 39 | signaturesKeyId = my-key-id 40 | signaturesKey = bd0c18188f2c1ed5ba917c7914ec25f31a6bd7e061edc081 41 | signaturesAlgorithm = hmac-sha256 42 | # signaturesHeader = 43 | # signaturesSignedHeaders = (request-target) date 44 | 45 | -------------------------------------------------------------------------------- /src/main/tomee/tomee.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | JdbcDriver org.hsqldb.jdbcDriver 23 | JdbcUrl jdbc:hsqldb:file:target/db/moviefun 24 | UserName sa 25 | Password 26 | 27 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | index.jsp 22 | 23 | 24 | 25 | 26 | default 27 | /app/* 28 | 29 | 30 | default 31 | /webjars/* 32 | 33 | 34 | 36 | 37 | application 38 | /index.jsp 39 | 40 | 41 | application 42 | /* 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/webapp/app/app.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | html { 19 | height: 100%; 20 | } 21 | 22 | body { 23 | @content-margin-top: 45px; 24 | background-color: #f5f5f5; 25 | 26 | &[current-view="ux-login"] { 27 | &, .container, .ux-content-area, .ux-login { 28 | height: 100%; 29 | } 30 | .back-drop { 31 | content: ''; 32 | display: block; 33 | z-index: -1; 34 | position: fixed; 35 | background: none center center no-repeat; 36 | background-size: cover; 37 | top: 0; 38 | left: 0; 39 | right: 0; 40 | bottom: 0; 41 | &.login { 42 | background-image: url(img/login.jpg); 43 | } 44 | &.shadow { 45 | background-color: rgba(57, 53, 102, 0.9) 46 | } 47 | } 48 | 49 | .navbar { 50 | display: none; 51 | } 52 | 53 | .form-control { 54 | border-color: rgba(76,69,136,0.9); 55 | border-radius: .2rem; 56 | } 57 | .btn-lg.btn-primary { 58 | font-size: 1em; 59 | border-radius: .2rem; 60 | } 61 | } 62 | 63 | .bg-dark { 64 | background-color: #524e80 !important; 65 | } 66 | 67 | .navbar-brand.logo { 68 | padding-left: 41px; 69 | background: url(img/movie-icon.png) 5px center no-repeat; 70 | background-size: 30px; 71 | } 72 | .logout-icon { 73 | width: 20px; 74 | height: 20px; 75 | background: url(img/logout-icon.png) center center no-repeat; 76 | background-size: cover; 77 | } 78 | 79 | 80 | .ux-movie-window { 81 | .modal-dialog { 82 | padding-top: 90px; 83 | padding-bottom: 0; 84 | } 85 | } 86 | 87 | .btn-primary { 88 | background-color: #4ea1ff; 89 | border-color: rgba(78, 161, 255, 0.7); 90 | } 91 | 92 | .ux-jug { 93 | white-space: pre-wrap; 94 | color: #ffffff; 95 | } 96 | 97 | .ux-login { 98 | display: -ms-flexbox; 99 | display: flex; 100 | flex-direction: column; 101 | -ms-flex-align: center; 102 | align-items: stretch; 103 | color: #ffffff; 104 | justify-content: center; 105 | .text-muted { 106 | color: #f5f5f5!important; 107 | } 108 | .form-login { 109 | width: 100%; 110 | max-width: 330px; 111 | padding: 15px; 112 | margin: auto; 113 | .checkbox { 114 | font-weight: 400; 115 | } 116 | .form-control { 117 | position: relative; 118 | box-sizing: border-box; 119 | height: auto; 120 | padding: 10px; 121 | color: #ffffff; 122 | background-color: rgba(76,69,136,1); 123 | font-size: 16px; 124 | margin: 15px 0; 125 | &::placeholder{ 126 | color: #ffffff; 127 | } 128 | } 129 | .form-control:focus { 130 | z-index: 2; 131 | } 132 | img { 133 | width: 220px; 134 | } 135 | } 136 | } 137 | 138 | .ux-movie-page { 139 | margin-top: @content-margin-top; 140 | } 141 | 142 | .ux-landing { 143 | margin-top: @content-margin-top; 144 | } 145 | 146 | .ux-setup { 147 | margin-top: @content-margin-top; 148 | } 149 | 150 | .ux-main { 151 | margin-top: @content-margin-top; 152 | 153 | .pagination { 154 | margin-top: 0px; 155 | margin-bottom: 0px; 156 | } 157 | 158 | th:last-child { 159 | width: 5px; 160 | } 161 | 162 | a.ux-delete-row:hover, a.ux-edit-row:hover { 163 | cursor: hand; 164 | } 165 | 166 | .panel-body>.input-group { 167 | margin-bottom: 15px; 168 | } 169 | 170 | .search-movies-bar { 171 | height: 50px; 172 | @search-bar-color: #524e80; 173 | border: 1px solid @search-bar-color; 174 | 175 | span.input-group-text, input, #dropdownMenuButton { 176 | color: #FFFFFF; 177 | background-color: @search-bar-color; 178 | } 179 | 180 | span.input-group-text span.fa { 181 | padding-left: 10px; 182 | } 183 | 184 | input::placeholder { 185 | color: #FFFFFF; 186 | } 187 | } 188 | } 189 | 190 | .alert.fixed { 191 | position: fixed; 192 | bottom: 20px; 193 | margin-left: -200px; 194 | left: 50%; 195 | width: 400px; 196 | margin-bottom: 0; 197 | box-shadow: 1px 1px 4px -1px #454545; 198 | z-index: 1051; 199 | } 200 | 201 | .ux-logout-block { 202 | .ux-avatar { 203 | box-shadow: 0 0 0 2px #fff; 204 | } 205 | } 206 | } 207 | 208 | /* enable absolute positioning */ 209 | .inner-addon { 210 | position: relative; 211 | } 212 | 213 | /* style icon */ 214 | .inner-addon .fa { 215 | position: absolute; 216 | padding: 16px 13px; 217 | pointer-events: none; 218 | z-index: 3; 219 | font-size: 14px; 220 | color: #fff; 221 | top: 0; 222 | } 223 | 224 | /* align icon */ 225 | .left-addon .fa { left: 0;} 226 | .right-addon .fa { right: 0;} 227 | 228 | /* add padding */ 229 | .left-addon input { padding-left: 35px!important; } 230 | .right-addon input { padding-right: 35px!important; } 231 | 232 | .text-muted { color: #9e9e9e!important; } 233 | .text-light-gray { color: #bac5cd!important; } 234 | 235 | .modal-content{ 236 | .close-x { 237 | position: absolute; 238 | right: -1em; 239 | top: -1em; 240 | border: 2px solid #fff; 241 | border-radius: 50%; 242 | color: #fff; 243 | height: 1em; 244 | width: 1em; 245 | line-height: 0; 246 | padding: 0 0 0.25em 0; 247 | opacity: 1; 248 | } 249 | 250 | .modal-footer { 251 | button.ux-close { 252 | color: #B9B9B9; 253 | font-size: 14px; 254 | } 255 | button.ux-save { 256 | width: 7rem; 257 | } 258 | } 259 | } 260 | 261 | .thead-gray { 262 | background-color: #e5e5e5; 263 | color: #9e9e9e; 264 | } 265 | 266 | .tbody-gray { 267 | color: #a7a7a7; 268 | a { 269 | color: #5f4a92; 270 | } 271 | } 272 | 273 | .modal-backdrop { 274 | background-color: #534E7D; 275 | &.show { 276 | opacity: 0.9; 277 | } 278 | } 279 | 280 | .ux-hover-dots { 281 | font-size: 1.5em; 282 | &:hover, .btn-group.show > & { 283 | color: #5496f3; 284 | } 285 | } 286 | 287 | .ux-hover-dots, .dropdown-item { 288 | cursor: pointer; 289 | } 290 | 291 | .dropdown-item.active, .dropdown-item:active { 292 | span{ 293 | color: #fff!important; 294 | } 295 | } 296 | 297 | *:focus { 298 | box-shadow: none !important; 299 | } 300 | 301 | -------------------------------------------------------------------------------- /src/main/webapp/app/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | var APP_CONFIG = { 20 | baseUrl: window.ux.ROOT_URL, 21 | paths: { 22 | 'text': 'webjars/requirejs-text/2.0.15/text', 23 | 'lib/less': 'webjars/less/2.7.2/less.min', 24 | 'lib/jquery': 'webjars/jquery/3.3.1/jquery.min', 25 | 'lib/bootstrap': 'webjars/bootstrap/4.1.0/js/bootstrap.bundle.min', 26 | 'lib/handlebars': 'webjars/handlebars/4.0.6/handlebars.min', 27 | 'lib/underscore': 'webjars/underscorejs/1.8.3/underscore-min', 28 | 'lib/json2': 'webjars/json2/20140204/json2.min', 29 | 'backbone': 'webjars/backbonejs/1.3.3/backbone', 30 | 'jwt_decode': 'webjars/jwt-decode/2.2.0/build/jwt-decode.min', 31 | 'jwk-js': 'app/js/tools/jwk-js.umd.min', 32 | 'http-signatures-js': 'app/js/tools/http-signatures-js.umd.min', 33 | 'header-wrapper': 'app/js/tools/header-wrapper', 34 | 'lib/crypto': 'webjars/crypto-js/3.1.9-1/crypto-js', 35 | 'lib/moment': 'webjars/momentjs/2.22.1/moment', 36 | 'lib/backbone-localstorage': 'webjars/backbone-localstorage/1.1.16/backbone.localStorage-min' 37 | }, 38 | shim: { 39 | 'lib/bootstrap': { 40 | deps: ['lib/jquery'] 41 | }, 42 | 'lib/underscore': { 43 | exports: '_' 44 | }, 45 | 'backbone': { 46 | deps: ['lib/jquery', 'lib/json2', 'lib/underscore'] 47 | }, 48 | 'app/js/templates': { 49 | deps: ['lib/underscore', 'app/js/tools/i18n'] 50 | }, 51 | 'lib/backbone-localstorage': { 52 | deps: ['backbone'], 53 | exports: 'Store' 54 | } 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /src/main/webapp/app/img/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomitribe/microservice-with-jwt-and-microprofile/23c2705193e73d7dd80f5f79219d0bb962df8060/src/main/webapp/app/img/login.jpg -------------------------------------------------------------------------------- /src/main/webapp/app/img/logout-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomitribe/microservice-with-jwt-and-microprofile/23c2705193e73d7dd80f5f79219d0bb962df8060/src/main/webapp/app/img/logout-icon.png -------------------------------------------------------------------------------- /src/main/webapp/app/img/movie-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomitribe/microservice-with-jwt-and-microprofile/23c2705193e73d7dd80f5f79219d0bb962df8060/src/main/webapp/app/img/movie-icon.png -------------------------------------------------------------------------------- /src/main/webapp/app/img/movie-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomitribe/microservice-with-jwt-and-microprofile/23c2705193e73d7dd80f5f79219d0bb962df8060/src/main/webapp/app/img/movie-logo.png -------------------------------------------------------------------------------- /src/main/webapp/app/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = [ 23 | 'app/js/view/container', 24 | 'app/js/view/login', 25 | 'app/js/view/main', 26 | 'app/js/view/main-table-paginator', 27 | 'app/js/view/movie', 28 | 'app/js/view/movie-page.view', 29 | 'lib/underscore', 30 | 'app/js/model/movies', 31 | 'app/js/model/movie', 32 | 'app/js/model/auth', 33 | 'app/js/tools/i18n', 34 | 'app/js/tools/alert.view', 35 | 'lib/less', 'backbone', 'lib/jquery', 'lib/bootstrap' 36 | ]; 37 | define(deps, function (containerView, loginView, mainView, paginator, MovieView, MoviePageView, underscore, moviesList, MovieModel, AuthModel, i18n, AlertView) { 38 | var auth = new AuthModel({id: 'ux.auth'}); 39 | auth.fetch(); 40 | window.ux.auth = auth; 41 | var max = 5; 42 | var appState = { 43 | page: null, 44 | fieldName: null, 45 | fieldValue: null, 46 | movieId: null 47 | }; 48 | containerView.render(); 49 | var router = null; 50 | 51 | $.ajaxSetup({ cache: false }); 52 | 53 | function loadPage(pageNumber, fieldName, fieldValue) { 54 | 55 | auth.getAuth().then( 56 | function () { 57 | pageLoading(pageNumber, fieldName, fieldValue); 58 | } 59 | ).catch( function () { 60 | router.navigate('login', { 61 | trigger: true 62 | }); 63 | } 64 | ) 65 | } 66 | 67 | function pageLoading(pageNumber, fieldName, fieldValue) { 68 | 69 | var data = { 70 | max: max, 71 | first: ((pageNumber - 1) * max) 72 | }; 73 | if (fieldName && fieldValue) { 74 | data.field = fieldName; 75 | data.searchTerm = fieldValue; 76 | } 77 | mainView.setFilter(fieldName, fieldValue); 78 | moviesList.fetch({ 79 | data: data, 80 | success: function (result) { 81 | mainView.addRows(result.models); 82 | }, 83 | complete: function () { 84 | var count = 0; 85 | function updCount(count){ 86 | paginator.setCount(count); 87 | mainView.setPaginator(count); 88 | if (!!count && pageNumber > count) { 89 | router.showMain(count, fieldName, fieldValue); 90 | } 91 | } 92 | $.ajax({ 93 | url: window.ux.ROOT_URL + 'rest/movies/count/', 94 | method: 'GET', 95 | dataType: 'json', 96 | data: { 97 | field: appState.fieldName, 98 | searchTerm: appState.fieldValue 99 | }, 100 | success: function (total) { 101 | count = Math.ceil(total / max); 102 | updCount(count); 103 | }, 104 | complete: function () { 105 | updCount(count); 106 | } 107 | }) 108 | } 109 | }); 110 | } 111 | 112 | function start() { 113 | //Starting the backbone router. 114 | var Router = Backbone.Router.extend({ 115 | routes: { 116 | '': 'showMain', 117 | 'main(/:page)': 'showMain', 118 | 'main/:page/:field/:value': 'showMain', 119 | 'login(/:tail)': 'showLogin', 120 | 'logout(/:tail)': 'showLogout', 121 | 'movie/:id': 'showMovie', 122 | '*path': 'defaultRoute' 123 | }, 124 | showLogin: function () { 125 | auth.getAuth().then( function () { 126 | router.navigate('main/1', { 127 | trigger: true 128 | }); 129 | }).catch( function () { 130 | containerView.showView(loginView); 131 | }) 132 | }, 133 | showLogout: function () { 134 | window.ux.auth.logout() 135 | .then( 136 | function () { 137 | router.navigate('login', { 138 | trigger: true 139 | }); 140 | AlertView.show('Success', 'logged out', 'success'); 141 | } 142 | ) 143 | }, 144 | defaultRoute: function () { 145 | var me = this; 146 | me.navigate('main/1', { 147 | trigger: true 148 | }); 149 | }, 150 | showMovie: function (id) { 151 | var me = this; 152 | //appState.movieId = id; 153 | if (!id) { 154 | return me.navigate('main/1', { 155 | trigger: true 156 | }); 157 | } 158 | 159 | auth.getAuth().then( 160 | function () { 161 | $.ajax({ 162 | url: window.ux.ROOT_URL + 'rest/movies/' + id, 163 | method: 'GET', 164 | dataType: 'json', 165 | success: function (data) { 166 | var view = new MoviePageView({ 167 | model: new MovieModel(data) 168 | }); 169 | view.render(); 170 | view.on('edit', function (data) { 171 | showMovieWindow(data.model) 172 | .then(function() { 173 | view.render(); 174 | }).catch(_.noop); 175 | }); 176 | 177 | containerView.showView(view); 178 | } 179 | }); 180 | } 181 | ).catch( function () { 182 | router.navigate('login', { 183 | trigger: true 184 | }); 185 | } 186 | ) 187 | }, 188 | showMain: function (page, fieldName, fieldValue) { 189 | var me = this; 190 | appState.page = page; 191 | appState.fieldName = fieldName; 192 | appState.fieldValue = fieldValue; 193 | containerView.showView(mainView); 194 | if (!page || !underscore.isNumber(Number(page))) { 195 | me.showMain(1); 196 | } else { 197 | loadPage(Number(page), fieldName, fieldValue); 198 | paginator.setPage(Number(page)); 199 | if (fieldName) { 200 | me.navigate('main/' + page + '/' + fieldName + '/' + fieldValue, { 201 | trigger: false 202 | }); 203 | } else { 204 | me.navigate('main/' + page, { 205 | trigger: false 206 | }); 207 | } 208 | } 209 | } 210 | }); 211 | 212 | router = new Router(); 213 | 214 | mainView.on('load-sample', function () { 215 | $.ajax({ 216 | url: window.ux.ROOT_URL + 'rest/load/', 217 | method: 'POST', 218 | dataType: 'json', 219 | data: {}, 220 | success: function (data) { 221 | router.showMain(); 222 | }, 223 | error: function (e) { 224 | if (e.status === 403) { 225 | AlertView.show('Failed', 'Failed to load movies (forbidden)', 'danger'); 226 | } 227 | } 228 | }); 229 | }); 230 | 231 | mainView.on('delete', function (data) { 232 | data.model.destroy({ 233 | success: function () { 234 | router.showMain(appState.page, appState.fieldName, appState.fieldValue); 235 | }, 236 | error: function (model, jqXHR) { 237 | if (jqXHR.status === 403){ 238 | AlertView.show('Failed', 'Failed to delete movie (forbidden)', 'danger'); 239 | } 240 | } 241 | }); 242 | }); 243 | 244 | function showMovieWindow(model, nw) { 245 | return new Promise(function(res, rej) { 246 | var view = new MovieView({ 247 | model: model 248 | }); 249 | view.render(); 250 | view.on('save-model', function (data) { 251 | data.model.save({}, { 252 | success: function () { 253 | view.$el.modal('hide'); 254 | view.remove(); 255 | res(); 256 | }, 257 | error: function (model, jqXHR) { 258 | if (jqXHR.status === 403) { 259 | AlertView.show('Failed', 'Failed to ' + (nw ? 'create' : 'update') + ' movie (forbidden)', 'danger'); 260 | } 261 | rej(); 262 | } 263 | }); 264 | }); 265 | $('body').append(view.$el); 266 | view.$el.modal({}); 267 | 268 | }); 269 | } 270 | 271 | mainView.on('add', function () { 272 | showMovieWindow(new MovieModel({}), true) 273 | .then(function() { 274 | loadPage(appState.page, appState.fieldName, appState.fieldValue); 275 | }).catch(_.noop); 276 | }); 277 | 278 | mainView.on('edit', function (data) { 279 | showMovieWindow(data.model) 280 | .then(function() { 281 | loadPage(appState.page, appState.fieldName, appState.fieldValue); 282 | }).catch(_.noop); 283 | }); 284 | 285 | mainView.on('movie', function (data) { 286 | router.navigate('movie/' + data.model.id, { 287 | trigger: true 288 | }); 289 | }); 290 | 291 | mainView.on('filter', function (data) { 292 | router.navigate('main/1/' + data.filterType + '/' + data.filterValue, { 293 | trigger: true 294 | }); 295 | }); 296 | 297 | mainView.on('clear-filter', function (data) { 298 | router.navigate('main/1', { 299 | trigger: true 300 | }); 301 | }); 302 | 303 | paginator.on('go-to-page', function (data) { 304 | var page = data.number; 305 | router.showMain(page, appState.fieldName, appState.fieldValue); 306 | }); 307 | 308 | //Starting the backbone history. 309 | Backbone.history.start({ 310 | pushState: true, 311 | root: window.ux.ROOT_URL // This value is set by 312 | }); 313 | 314 | return { 315 | getRouter: function () { 316 | return router; 317 | } 318 | }; 319 | } 320 | 321 | return { 322 | start: start, 323 | auth: auth 324 | }; 325 | }); 326 | }()); 327 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/model/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['lib/underscore', 'backbone', 'jwt_decode', 'app/js/model/login', 'lib/moment', 'app/js/tools/alert.view', 'lib/backbone-localstorage', 'jwk-js', 'http-signatures-js', 'header-wrapper']; 23 | define(deps, function (_, Backbone, jwtDecode, LoginModel, moment, AlertView, bbLocalstorage, jwkJs, httpSignaturesJs, headerWrapper) { 24 | var AuthModel = Backbone.Model.extend({ 25 | id: 'ux.auth', 26 | localStorage: new Store('ux.auth'), 27 | defaults: { 28 | auth: false, 29 | username: '', 30 | email: '', 31 | groups: '', 32 | jug: '', 33 | 34 | access_token: '', 35 | access_exp: '', 36 | 37 | token_type: '', 38 | expires_in: '', 39 | 40 | refresh_token: '', 41 | refresh_exp: '' 42 | }, 43 | loginModel: null, 44 | chRef: null, 45 | initialize: function () { 46 | var me = this; 47 | me.loginModel = new LoginModel(); 48 | me.chRef = _.throttle(me.checkRefresh, 500); 49 | window.headerWrapper = headerWrapper; 50 | $.ajaxSetup({ 51 | beforeSend: function (jqXHR) { 52 | headerWrapper.wrapXHR(jqXHR); 53 | jqXHR.setRequestHeader('tt-date', new Date().toGMTString()); 54 | 55 | // Simplest way to inject Authorization header for jQuery 56 | /*var access_token = me.get('access_token'), token_type = me.get('token_type') + " "; 57 | if (typeof access_token !== 'undefined' && !!access_token) { 58 | jqXHR.setRequestHeader('Authorization', token_type + access_token); 59 | }*/ 60 | } 61 | }); 62 | 63 | window.httpSignaturesJs = httpSignaturesJs; 64 | window.jwkJs = jwkJs.jwkJs; 65 | $.ajaxTransport("+*", function (options, originalOptions, jqXHR) { 66 | me.chRef(); 67 | if (!originalOptions.ignoreTransport) { 68 | const transport = { 69 | options, 70 | originalOptions, 71 | jqXHR, 72 | cb: null, 73 | abort: function (message) { 74 | this.cb(400, message || 'request failed'); 75 | }, 76 | send: async function (retryRequest) { 77 | if (me.loggingOut) return this.abort(); 78 | 79 | const access_token = me.get('access_token'), token_type = me.get('token_type') + " ", 80 | possessor_key = me.get('possessor_key'); 81 | if (typeof access_token !== 'undefined' && !!access_token) { 82 | if (typeof possessor_key !== 'undefined' && !!possessor_key) { 83 | const keyObj = JSON.parse(window.jwkJs.bu2s(possessor_key)); 84 | const signingString = new window.httpSignaturesJs.Signatures.createSigningString(['tt-date'], originalOptions.method, originalOptions.url, jqXHR.requestHeaders ); 85 | const signature = await window.jwkJs 86 | .tryPromise(() => window.jwkJs.HMAC.sign('256')(signingString, possessor_key)) 87 | .then(signature => signature) 88 | .catch(console.error); 89 | if (signature) { 90 | console.log(signingString, possessor_key, signature); 91 | const signatureHeader = new window.httpSignaturesJs.Signature( 92 | keyObj.kid, 93 | "hmac-sha256", 94 | signature, 95 | ['tt-date'] 96 | ); 97 | this.originalOptions.headers = { 98 | ...this.originalOptions.headers, 99 | Signature: signatureHeader.toString() 100 | }; 101 | } 102 | this.originalOptions.headers = { 103 | ...this.originalOptions.headers, 104 | Authorization: 'Bearer ' + access_token 105 | }; 106 | } else { 107 | // Another way to inject Authorization header 108 | this.originalOptions.headers = { 109 | ...this.originalOptions.headers, 110 | Authorization: token_type + access_token 111 | }; 112 | } 113 | } 114 | $.ajax({ 115 | ...this.originalOptions, 116 | ignoreTransport: true, 117 | retryRequest 118 | }).done((data, status, xhr) => { 119 | this.cb(200, status, {text: xhr.responseText, JSON: xhr.responseJSON}); 120 | }).fail(xhr => { 121 | if (me.loggingOut) return this.abort(); 122 | 123 | const now = moment().valueOf(), 124 | refresh_exp = me.get('refresh_exp'), 125 | leftRe = (refresh_exp - now); 126 | if (!refresh_exp || leftRe < 0) { 127 | me.logoutAndWarn(xhr); 128 | } else if (me.checkRefStatus) { 129 | setTimeout(() => { 130 | this.send(0); 131 | }, 200); 132 | } else if (xhr.status === 401 && retryRequest < 2) { 133 | me.checkRefStatus = true; 134 | me.refresh() 135 | .then(() => this.send(retryRequest + 1)) 136 | .catch(e => me.logoutAndWarn(e)) 137 | .then(() => me.checkRefStatus = false); 138 | } else { 139 | this.abort(); 140 | } 141 | }); 142 | } 143 | }; 144 | return { 145 | send: function (headers, cb) { 146 | transport.cb = cb; 147 | transport.send(0); 148 | }, 149 | abort: function () { 150 | transport.abort(); 151 | } 152 | }; 153 | } 154 | }); 155 | 156 | var originalNavigate = Backbone.history.navigate; 157 | Backbone.history.navigate = function (fragment, options) { 158 | originalNavigate.apply(this, arguments); 159 | }; 160 | }, 161 | loggingOut: false, 162 | logoutAndWarn: function (e) { 163 | var me = this; 164 | if (me.loggingOut) return; 165 | 166 | const message = e && e.responseJSON && e.responseJSON.error_description; 167 | me.loggingOut = true; 168 | 169 | me.logout().then( 170 | function () { 171 | if (!window.BackboneApp) return window.open('login', "_self", false); 172 | const router = window.BackboneApp.getRouter(); 173 | router.navigate('login', { 174 | trigger: true 175 | }); 176 | AlertView.show('Warning', message || 'Your access has expired', 'warning'); 177 | setTimeout(() => me.loggingOut = false, 300); 178 | } 179 | ); 180 | }, 181 | checkRefStatus: false, 182 | checkRefresh: function () { 183 | var me = this; 184 | if (!window.BackboneApp || me.checkRefStatus) return; 185 | 186 | me.checkRefStatus = true; 187 | me.getAuth() 188 | .then(function () { 189 | const now = moment().valueOf(), 190 | access_exp = me.get('access_exp'), 191 | left = access_exp && (access_exp - now), 192 | refresh_exp = me.get('refresh_exp'), 193 | leftRe = (refresh_exp - now); 194 | if (!refresh_exp || leftRe < 0) { 195 | me.logoutAndWarn(); 196 | } else if (!access_exp || left < 0) { 197 | return me.refresh() 198 | .then(_.noop); 199 | } 200 | }) 201 | .catch(e => e && me.logoutAndWarn(e)) 202 | .then(function () { 203 | me.checkRefStatus = false 204 | }); 205 | }, 206 | login: function (creds) { 207 | var me = this; 208 | return new Promise(function (res, rej) { 209 | me.loginModel.getAccess(creds) 210 | .then(function (resp) { 211 | me.parseResp(resp); 212 | me.save(); 213 | me.getAuth().then(res).catch(rej); 214 | }) 215 | .catch(rej); 216 | }) 217 | }, 218 | logout: function () { 219 | var me = this; 220 | return new Promise(function (res, rej) { 221 | me.parseResp(); 222 | me.save(); 223 | res(!me.get('auth')); 224 | }); 225 | }, 226 | refresh: function () { 227 | var me = this; 228 | return new Promise(function (res, rej) { 229 | const rt = me.get('refresh_token'); 230 | if (!rt) return rej('no token to refresh'); 231 | me.loginModel.getRefresh(rt) 232 | .then(function (resp) { 233 | me.parseResp(resp); 234 | me.save(); 235 | me.getAuth().then(res).catch(rej); 236 | }) 237 | .catch(rej); 238 | }) 239 | }, 240 | parseResp: function (resp) { 241 | if (typeof resp === 'string') { 242 | try { 243 | const json = JSON.parse(resp); 244 | resp = json; 245 | } catch (e) { 246 | 247 | } 248 | } 249 | var me = this; 250 | var access_token = resp && resp['access_token'] && jwtDecode(resp['access_token']); 251 | var refresh_token = resp && resp['refresh_token'] && jwtDecode(resp['refresh_token']); 252 | var possessor_key = resp && resp['key']; 253 | var possessor_key_id = possessor_key && JSON.parse(atob(possessor_key)).kid; 254 | if (resp && resp['access_token'] && access_token) { 255 | const access_exp = moment.unix(access_token.exp).valueOf(), 256 | refresh_exp = moment.unix(refresh_token.exp).valueOf(); 257 | me.set({ 258 | auth: true, 259 | username: access_token['username'], 260 | email: access_token['email'], 261 | groups: access_token['groups'], 262 | jug: access_token['jug'], 263 | 264 | access_token: resp['access_token'], 265 | possessor_key: possessor_key, 266 | possessor_key_id: possessor_key_id, 267 | access_exp: access_exp, 268 | 269 | token_type: resp['token_type'], 270 | expires_in: resp['expires_in'], 271 | 272 | refresh_token: resp['refresh_token'], 273 | refresh_exp: refresh_exp 274 | }); 275 | } else { 276 | me.set({ 277 | auth: false, 278 | username: '', 279 | email: '', 280 | groups: '', 281 | jug: '', 282 | 283 | access_token: '', 284 | possessor_key: '', 285 | possessor_key_id: '', 286 | access_exp: '', 287 | 288 | token_type: '', 289 | expires_in: '', 290 | 291 | refresh_token: '', 292 | refresh_exp: '' 293 | }); 294 | } 295 | }, 296 | getAuth: function () { 297 | var me = this; 298 | return new Promise(function (res, rej) { 299 | me.get('auth') ? res() : rej(); 300 | }) 301 | } 302 | }); 303 | return AuthModel; 304 | 305 | }); 306 | }()); 307 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/model/login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['lib/underscore', 'backbone']; 23 | define(deps, function (_, Backbone) { 24 | var LoginModel = Backbone.Model.extend({ 25 | urlRoot: window.tokenHost || (window.ux.ROOT_URL + 'rest/token'), 26 | initialize: function () { 27 | }, 28 | getAccess: function(creds) { 29 | var me = this; 30 | return new Promise( function (res, rej) { 31 | if(!creds || !creds.length) return rej({'responseJSON':{'error_description': 'Credentials are required'}}); 32 | $.ajax({ 33 | method: "POST", 34 | url: me.urlRoot, 35 | headers: {date: new Date().toISOString()}, 36 | beforeSend: function() { 37 | }, 38 | global: false, 39 | ignoreTransport: true, 40 | data: creds, 41 | contentType: 'application/x-www-form-urlencoded' 42 | }) 43 | .done(res) 44 | .fail(rej); 45 | }); 46 | }, 47 | getRefresh: function(token) { 48 | var me = this; 49 | return new Promise( function (res, rej) { 50 | if(!token || !token.length) return rej({'responseJSON':{'error_description': 'No token'}}); 51 | $.ajax({ 52 | method: "POST", 53 | url: me.urlRoot, 54 | beforeSend: function() { 55 | }, 56 | global: false, 57 | ignoreTransport: true, 58 | data: $.param({ 59 | refresh_token: token, 60 | //type: 'oauth2', 61 | grant_type: 'refresh_token' 62 | }), 63 | contentType: 'application/x-www-form-urlencoded' 64 | }) 65 | .done(res) 66 | .fail(rej); 67 | }); 68 | } 69 | }); 70 | return LoginModel; 71 | 72 | }); 73 | }()); 74 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/model/movie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['backbone']; 23 | define(deps, function () { 24 | var isString = function (obj) { 25 | return Object.prototype.toString.call(obj) === '[object String]'; 26 | }; 27 | return Backbone.Model.extend({ 28 | urlRoot: window.ux.ROOT_URL + 'rest/movies', 29 | idAttribute: 'id', 30 | toJSON: function () { 31 | if (!!this.attributes.rating && isString(this.attributes.rating)) { 32 | this.attributes.rating = parseInt(this.attributes.rating, 10); 33 | } 34 | if (!!this.attributes.year && isString(this.attributes.year)) { 35 | this.attributes.year = parseInt(this.attributes.year, 10); 36 | } 37 | if (!!this.attributes.id && isString(this.attributes.id)) { 38 | this.attributes.id = parseInt(this.attributes.id, 10); 39 | } 40 | return this.attributes; 41 | }, 42 | defaults: { 43 | rating: 5, 44 | year: new Date().getFullYear() 45 | } 46 | }); 47 | }); 48 | }()); 49 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/model/movies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['app/js/model/movie', 'backbone']; 23 | define(deps, function (Movie) { 24 | var Cls = Backbone.Collection.extend({ 25 | model: Movie, 26 | url: window.ux.ROOT_URL + 'rest/movies', 27 | parse: function (response) { 28 | return response; 29 | } 30 | }); 31 | return new Cls(); 32 | }); 33 | }()); 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/start.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | require.config(APP_CONFIG); 20 | requirejs(['header-wrapper'], function (headerWrapper) { 21 | const res = headerWrapper.wrapXHR(XMLHttpRequest || window.XMLHttpRequest); 22 | if(res.headerWrapped) { 23 | window.XMLHttpRequest = res.xhr; 24 | } 25 | requirejs(['app/js/app'], function (app) { 26 | 'use strict'; 27 | $(window.document).ready(function () { 28 | // all the action is in app 29 | window.BackboneApp = app.start(); 30 | window.ux.auth = app.auth; 31 | }); 32 | }); 33 | }); 34 | 35 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var files = [ 23 | 'container', 24 | 'login', 25 | 'main', 26 | 'main-table-row', 27 | 'main-table-paginator-button', 28 | 'load-data-link', 29 | 'movie', 30 | 'movie-page', 31 | 'alert' 32 | ]; 33 | 34 | function loop(values, callback) { 35 | var index; 36 | for (index = 0; index < values.length; index += 1) { 37 | callback(values[index], index); 38 | } 39 | } 40 | 41 | // Preparing the "requirements" paths. 42 | var requirements = ['lib/handlebars']; 43 | loop(files, function (file) { 44 | requirements.push('text!app/js/templates/' + file + '.handlebars'); 45 | }); 46 | 47 | define(requirements, function (Handlebars) { 48 | var templates = {}; 49 | 50 | var myArgs = arguments; 51 | loop(files, function (file, i) { 52 | templates[file] = Handlebars.compile(myArgs[i+1]); 53 | }); 54 | 55 | return { 56 | getValue: function (templateName, cfg) { 57 | var template = templates[templateName]; 58 | if (!template) { 59 | throw 'Template not registered. "' + templateName + '"'; 60 | } 61 | var result; 62 | if (cfg) { 63 | result = template(cfg); 64 | } else { 65 | result = template({}); 66 | } 67 | return result; 68 | } 69 | }; 70 | }); 71 | }()); 72 | 73 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates/alert.handlebars: -------------------------------------------------------------------------------- 1 | {{ title }} 2 | {{ message }} 3 | 6 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates/container.handlebars: -------------------------------------------------------------------------------- 1 |

16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates/load-data-link.handlebars: -------------------------------------------------------------------------------- 1 | {{i18n "load.dada"}} 2 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates/login.handlebars: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates/main-table-paginator-button.handlebars: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | {{pageText}} 4 | 5 |
  • 6 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates/main-table-row.handlebars: -------------------------------------------------------------------------------- 1 | {{title}} 2 | {{director}} 3 | {{genre}} 4 | {{rating}}/10 5 | {{year}} 6 | 7 |
    8 | 9 | 13 |
    14 | 15 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates/main.handlebars: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    {{i18n "movies"}}

    5 |
    6 |
    7 |
    8 |
    9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 | 16 | 17 | 18 |
    19 | 20 |
    21 | 24 | 31 |
    32 |
    33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    {{i18n "title"}}{{i18n "director"}}{{i18n "genre"}}{{i18n "rating"}}{{i18n "year"}}
    46 |
    47 |
    48 |
    49 |
    50 | 51 |
    52 |
    53 |
    54 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates/movie-page.handlebars: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    {{title}} ({{year}})

    5 |
    6 | 7 | 11 |
    12 |
    13 |
    14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    TITLE
    24 |
    {{title}}
    25 |
    26 |
    27 |
    DIRECTOR
    28 |
    {{director}}
    29 |
    30 |
    31 |
    GENRE
    32 |
    {{genre}}
    33 |
    34 |
    35 |
    36 |
    37 |
    RATING
    38 |
    {{rating}}/10
    39 |
    40 |
    41 |
    YEAR
    42 |
    {{year}}
    43 |
    44 |
    45 |
    46 |
    47 |
    48 |
    49 |
    50 |
    51 |
    52 |
    COMMENTS
    53 | {{#each comments}} 54 |
    55 |
    56 |
    57 | 60 |
    61 |
    62 |
    {{this.author}}
    63 | {{formatTime 65 | this.timestamp}} 66 |
    67 |

    {{this.comment}}

    68 |
    69 |
    70 |
    71 |
    72 | {{/each}} 73 |
    74 |
    75 |
    76 | 77 | 79 | 80 |
    81 |
    82 | 84 | 87 |
    88 |
    89 |
    90 |
    91 |
    92 |
    93 |
    94 |
    95 |
    96 |
    97 |
    98 |
    99 |
    100 |
    101 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/templates/movie.handlebars: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/tools/alert.view.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var deps = ['app/js/templates', 'lib/underscore', 'app/js/tools/i18n', 'backbone']; 5 | define(deps, function (templates, _, i18n, Backbone) { 6 | var AlertView = Backbone.View.extend({ 7 | tagName: "div", 8 | className: "alert", 9 | 10 | initialize: function (options) { 11 | this.options = Object.assign({ 12 | alert: "info", 13 | title: "", 14 | message: "", 15 | fixed: false 16 | }, options); 17 | }, 18 | 19 | render: function () { 20 | var that = this, 21 | output = templates.getValue('alert', { 22 | title: this.options.title, 23 | message: this.options.message 24 | }); 25 | 26 | this.$el.addClass("alert-" + this.options.alert).html(output).alert(); 27 | 28 | if (this.options.fixed) { 29 | this.$el.addClass("fixed"); 30 | } 31 | 32 | window.setTimeout(function () { 33 | that.$el.addClass("in"); 34 | }, 20); 35 | 36 | return this; 37 | }, 38 | 39 | remove: function () { 40 | var that = this; 41 | 42 | this.$el.removeClass("in"); 43 | 44 | window.setTimeout(function () { 45 | that.$el.remove(); 46 | }, 1000); 47 | } 48 | }); 49 | 50 | AlertView.show = function (title, message, alertType, timeout) { 51 | var alert = new AlertView({ 52 | alert: alertType, 53 | title: title, 54 | message: message, 55 | fixed: true 56 | }); 57 | 58 | $('.ux-content-area').append(alert.render().$el); 59 | 60 | window.setTimeout(function () { 61 | alert.remove(); 62 | }, timeout || 1500); 63 | 64 | return alert; 65 | }; 66 | 67 | return AlertView; 68 | }); 69 | })(); 70 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/tools/date.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | "use strict"; 18 | */ 19 | 20 | define(['lib/handlebars', 'lib/moment'], function (Handlebars, moment) { 21 | 'use strict'; 22 | 23 | function formatTime(dateStr) { 24 | var momentDate = moment(java2jsDate(dateStr)), 25 | nowDate = moment(), 26 | days = nowDate.diff(momentDate, 'days'); 27 | if (days < 4) { 28 | return momentDate.fromNow(); 29 | } 30 | else if(nowDate.year() === momentDate.year()){ 31 | return momentDate.format('DD MMMM'); 32 | } else { 33 | return momentDate.format('DD MMMM YYYY'); 34 | } 35 | } 36 | 37 | function java2jsDate(dateStr) { 38 | return moment(dateStr, "YYYYMMDDHHmmssZZ").valueOf(); 39 | } 40 | 41 | function java2jsString(dateStr) { 42 | return moment(dateStr, "YYYYMMDDHHmmssZZ").toString(); 43 | } 44 | 45 | function java2jsISO(dateStr) { 46 | return moment(dateStr, "YYYYMMDDHHmmssZZ").toISOString(); 47 | } 48 | 49 | Handlebars.registerHelper("formatTime", formatTime); 50 | Handlebars.registerHelper("java2jsString", java2jsString); 51 | Handlebars.registerHelper("java2jsISO", java2jsISO); 52 | Handlebars.registerHelper("java2jsDate", java2jsDate); 53 | 54 | return { 55 | formatTime: formatTime, 56 | java2jsString: java2jsString, 57 | java2jsISO: java2jsISO, 58 | java2jsDate: java2jsDate 59 | }; 60 | }); 61 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/tools/gravatar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | "use strict"; 18 | */ 19 | 20 | define(['lib/handlebars', 'lib/crypto'], function (Handlebars, CryptoJS) { 21 | 'use strict'; 22 | 23 | function gravatar(emailStr) { 24 | const gr = (emailStr || '').trim(); 25 | if (!gr || !gr.length) return "//www.gravatar.com/avatar/?s=90"; 26 | return "//www.gravatar.com/avatar/" + CryptoJS.MD5(gr).toString() + "?s=90&d=retro" 27 | } 28 | 29 | Handlebars.registerHelper("gravatar", gravatar); 30 | 31 | return { 32 | gravatar: gravatar 33 | }; 34 | }); 35 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/tools/header-wrapper.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | function wrapXHR(xhr){ 4 | const proto = (xhr.prototype||xhr); 5 | if(!proto.wrappedSetRequestHeader) { 6 | proto.wrappedSetRequestHeader = proto.setRequestHeader; 7 | proto.setRequestHeader = function (header, value) { 8 | //console.log('[debug] setRequestHeader("'+header+'", "'+value+'");'); 9 | this.wrappedSetRequestHeader(header, value); 10 | if (!this.requestHeaders) { 11 | this.requestHeaders = {}; 12 | } 13 | if (!this.requestHeaders[header]) { 14 | this.requestHeaders[header] = [value]; 15 | }else { 16 | this.requestHeaders[header].push(value); 17 | } 18 | }; 19 | return {headerWrapped: true, xhr}; 20 | } else return {headerWrapped: false, xhr}; 21 | } 22 | define([], function () { 23 | return {wrapXHR}; 24 | }) 25 | })(); 26 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/tools/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | "use strict"; 18 | */ 19 | 20 | define(['lib/underscore', 'lib/handlebars', 'app/js/tools/log'], function (_, Handlebars) { 21 | 'use strict'; 22 | 23 | var missing = Handlebars.compile('[!{{key}}!]'); 24 | var messages = { 25 | 'application.name': 'MovieChat', 26 | 'load.dada': 'Click here to load sample data', 27 | 'movies': 'Movies', 28 | 'title': 'Title', 29 | 'director': 'Director', 30 | 'genre': 'Genre', 31 | 'rating': 'Rating', 32 | 'year': 'Year', 33 | 'add.movie': 'Add movie', 34 | 'movie': 'Movie', 35 | 'close': 'Close', 36 | 'save': 'Save', 37 | 'cancel': 'cancel', 38 | 39 | "January": "January", 40 | "February": "February", 41 | "March": "March", 42 | "April": "April", 43 | "May": "May", 44 | "June": "June", 45 | "July": "July", 46 | "August": "August", 47 | "September": "September", 48 | "October": "October", 49 | "November": "November", 50 | "December": "December", 51 | 52 | 'dummy': '' 53 | }; 54 | 55 | _.each(_.keys(messages), function (key) { 56 | var template = Handlebars.compile(messages[key]); 57 | messages[key] = template; 58 | }); 59 | 60 | var get = function (key, values) { 61 | var template = messages[key]; 62 | var cfg = values; 63 | if (!template) { 64 | template = missing; 65 | cfg = { 66 | key: key 67 | }; 68 | window.console.error('Missing i18n message.', key); 69 | } 70 | return template(cfg); 71 | }; 72 | 73 | Handlebars.registerHelper('i18n', function (key) { 74 | return get(key); 75 | }); 76 | 77 | return { 78 | get: get 79 | }; 80 | }); 81 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/tools/id.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | "use strict"; 18 | */ 19 | 20 | define(['lib/underscore', 'lib/handlebars'], function (_, Handlebars) { 21 | 'use strict'; 22 | 23 | var value = _.uniqueId('moviefun_'); 24 | 25 | var current = function () { 26 | return value; 27 | }; 28 | var next = function () { 29 | value = _.uniqueId('moviefun_'); 30 | return value; 31 | }; 32 | Handlebars.registerHelper('id_current', function (key) { 33 | return current(); 34 | }); 35 | Handlebars.registerHelper('id_next', function (key) { 36 | return next(); 37 | }); 38 | 39 | return { 40 | current: current, 41 | next: next 42 | }; 43 | }); 44 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/tools/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | define([], function () { 20 | 'use strict'; 21 | 22 | var noOp = function () { 23 | return ''; // no-op 24 | }; 25 | 26 | if (!window.console) { 27 | window.console = {}; 28 | } 29 | 30 | function createIfNull(functionName) { 31 | if (!window.console[functionName]) { 32 | window.console[functionName] = noOp; 33 | } 34 | } 35 | 36 | createIfNull('error'); 37 | createIfNull('warn'); 38 | createIfNull('log'); 39 | createIfNull('info'); 40 | }); 41 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/view/container.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['app/js/templates', 'app/js/tools/i18n', 'backbone', 'app/js/tools/alert.view', 'app/js/tools/gravatar']; 23 | define(deps, function (templates, il8n, Backbone, AlertView, Gravatar) { 24 | var View = Backbone.View.extend({ 25 | initialize: function(options){ 26 | this.options = options || {}; 27 | }, 28 | el: 'body', 29 | events: { 30 | 'click .navbar-brand': function (evt) { 31 | evt.preventDefault(); 32 | var me = this, 33 | router = window.BackboneApp.getRouter(); 34 | router.navigate(evt.target.href, { 35 | trigger: true 36 | }); 37 | }, 38 | 'click .ux-logout': function (evt) { 39 | evt.preventDefault(); 40 | var me = this, 41 | router = window.BackboneApp.getRouter(); 42 | window.ux.auth.logout() 43 | .then( 44 | function () { 45 | router.navigate('login', { 46 | trigger: true 47 | }); 48 | AlertView.show('Success', 'logged out', 'success'); 49 | } 50 | ) 51 | } 52 | }, 53 | showView: function (view) { 54 | var me = this; 55 | me.$el.attr('current-view', view.className); 56 | var contentArea = me.$('.ux-content-area'); 57 | if (me.currentView) { 58 | me.currentView.$el.detach(); 59 | } 60 | me.currentView = view; 61 | me.currentView.render(); 62 | contentArea.append(me.currentView.el); 63 | 64 | window.ux.auth.getAuth().then( 65 | function () { 66 | me.$('.ux-username').text(window.ux.auth.get('username')); 67 | if(window.ux.auth.get('jug')) { 68 | me.$('.ux-jug').text("- " + window.ux.auth.get('jug')); 69 | } else { 70 | me.$('.ux-jug').text(""); 71 | } 72 | me.$('.ux-avatar').attr("src", Gravatar.gravatar(window.ux.auth.get('email'))); 73 | me.$('.ux-logout-block').show("fast"); 74 | } 75 | ).catch( 76 | function () { 77 | me.$('.ux-logout-block').hide( 78 | "fast", 79 | function(){ 80 | me.$('.ux-username').text(""); 81 | me.$('.ux-jug').text(""); 82 | me.$('.ux-avatar').attr("src", ""); 83 | } 84 | ); 85 | } 86 | ); 87 | 88 | if (view.renderCallback) { 89 | view.renderCallback(); 90 | } 91 | me.$('.ux-app-menu-item').removeClass('active'); 92 | var myMenuItem = me.$('li.ux-app-menu-item.' + view.className); 93 | myMenuItem.addClass('active'); 94 | }, 95 | 96 | render: function (reRender) { 97 | if (!reRender && this.options.isRendered) { 98 | return this; 99 | } 100 | var html = templates.getValue('container', { 101 | userName: window.ux.auth.get('username') 102 | }); 103 | this.$el.html(html); 104 | 105 | // render it only once 106 | this.options.isRendered = true; 107 | return this; 108 | } 109 | }); 110 | 111 | return new View({}); 112 | }); 113 | }()); 114 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/view/login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['app/js/templates', 'lib/underscore', 'app/js/tools/i18n', 'backbone', 'app/js/tools/alert.view']; 23 | define(deps, function (templates, underscore, i18n, Backbone, AlertView) { 24 | var View = Backbone.View.extend({ 25 | initialize: function(options){ 26 | this.options = options || {}; 27 | }, 28 | tagName: 'div', 29 | className: 'ux-login', 30 | filterOption: 'title', 31 | events: { 32 | 'submit .form-login': function (evt) { 33 | evt.preventDefault(); 34 | var me = this, 35 | creds = $(evt.target).serialize(), 36 | router = window.BackboneApp.getRouter(); 37 | window.ux.auth.login(creds).then( 38 | function () { 39 | router.navigate('main/1', { 40 | trigger: true 41 | }); 42 | } 43 | ).catch( 44 | function (e) { 45 | AlertView.show('Failed', e['responseJSON'] && e['responseJSON']['error_description'] || e, 'danger'); 46 | } 47 | ); 48 | } 49 | }, 50 | render: function () { 51 | var me = this; 52 | if (!this.options.isRendered) { 53 | me.$el.empty(); 54 | me.$el.append(templates.getValue('login', {})); 55 | me.options.isRendered = true; 56 | } 57 | return this; 58 | } 59 | }); 60 | return new View(); 61 | }); 62 | }()); 63 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/view/main-table-paginator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['app/js/templates', 'backbone']; 23 | define(deps, function (templates) { 24 | 25 | var View = Backbone.View.extend({ 26 | initialize: function(options){ 27 | this.options = options || {}; 28 | }, 29 | tagName: 'ul', 30 | className: 'pagination col-9', 31 | count: 0, 32 | page: 1, 33 | events: { 34 | 'click a': function (evt) { 35 | evt.preventDefault(); 36 | var me = this; 37 | var myLink = $(evt.target); 38 | var page = myLink.attr('data-page'); 39 | me.trigger('go-to-page', { 40 | number: page 41 | }); 42 | } 43 | }, 44 | 45 | render: function () { 46 | var me = this; 47 | me.$el.empty(); 48 | me.$el.append(templates.getValue('main-table-paginator-button', { 49 | pageNumber: '1', 50 | pageText: '<<' 51 | })); 52 | var i; 53 | for (i = 1; i < me.count + 1; i += 1) { 54 | me.$el.append(templates.getValue('main-table-paginator-button', { 55 | isActive: i === this.page, 56 | pageNumber: i, 57 | pageText: i 58 | })); 59 | } 60 | me.$el.append(templates.getValue('main-table-paginator-button', { 61 | pageNumber: me.count.toString(), 62 | pageText: '>>' 63 | })); 64 | return this; 65 | }, 66 | 67 | setPage: function (page) { 68 | var me = this; 69 | me.page = page || 1; 70 | me.render(); 71 | }, 72 | 73 | setCount: function (count) { 74 | var me = this; 75 | me.count = count; 76 | me.render(); 77 | }, 78 | 79 | getCount: function () { 80 | var me = this; 81 | return me.count; 82 | } 83 | }); 84 | return new View().render(); 85 | }); 86 | }()); 87 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/view/main-table-row.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['app/js/templates', 'backbone']; 23 | define(deps, function (templates, Backbone) { 24 | 25 | var View = Backbone.View.extend({ 26 | initialize: function(options){ 27 | this.options = options || {}; 28 | }, 29 | tagName: 'tr', 30 | events: { 31 | 'click .ux-delete-row': function (evt) { 32 | evt.preventDefault(); 33 | var me = this; 34 | me.trigger('delete', { 35 | model: me.model 36 | }); 37 | }, 38 | 'click .ux-edit-row': function (evt) { 39 | evt.preventDefault(); 40 | var me = this; 41 | me.trigger('edit', { 42 | model: me.model 43 | }); 44 | }, 45 | 'click .ux-goto-movie': function (evt) { 46 | evt.preventDefault(); 47 | var me = this; 48 | me.trigger('movie', { 49 | model: me.model 50 | }); 51 | } 52 | }, 53 | 54 | render: function () { 55 | var me = this; 56 | if (!this.options.isRendered) { 57 | me.$el.empty(); 58 | me.$el.append(templates.getValue('main-table-row', { 59 | title: me.model.get('title'), 60 | director: me.model.get('director'), 61 | genre: me.model.get('genre'), 62 | rating: me.model.get('rating'), 63 | year: me.model.get('year'), 64 | id: me.model.get('id') 65 | })); 66 | me.options.isRendered = true; 67 | } 68 | return this; 69 | } 70 | }); 71 | return View; 72 | }); 73 | }()); 74 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/view/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['app/js/templates', 'app/js/view/main-table-row', 'app/js/view/main-table-paginator', 23 | 'lib/underscore', 'app/js/tools/i18n', 'backbone']; 24 | define(deps, function (templates, TableRowView, paginator, underscore, i18n, Backbone) { 25 | 26 | var View = Backbone.View.extend({ 27 | initialize: function(options){ 28 | this.options = options || {}; 29 | }, 30 | tagName: 'div', 31 | className: 'ux-main', 32 | loadDataLink: $(templates.getValue('load-data-link', {})), 33 | filterOption: 'title', 34 | events: { 35 | 'click .ux-filter': function (evt) { 36 | evt.preventDefault(); 37 | var me = this; 38 | var selected = $(me.$el.find('.ux-selected-filter').get(0)); 39 | var myLink = $(evt.target); 40 | me.filterOption = myLink.attr('data-option'); 41 | selected.html(i18n.get(me.filterOption, {})); 42 | }, 43 | 'click .ux-filter-action': function (evt) { 44 | evt.preventDefault(); 45 | var me = this; 46 | me.filterAction($(me.$el.find('.ux-filter-field').get(0)).val()); 47 | }, 48 | 'click .ux-clear-filter-action': function (evt) { 49 | evt.preventDefault(); 50 | var me = this; 51 | me.filterAction(); 52 | }, 53 | 'click .ux-main': function (evt) { 54 | evt.preventDefault(); 55 | var me = this; 56 | me.trigger('navigate', { 57 | path: 'main' 58 | }); 59 | }, 60 | 'click .ux-add-btn': function (evt) { 61 | evt.preventDefault(); 62 | var me = this; 63 | me.trigger('add', {}); 64 | }, 65 | 'keydown .ux-filter-field': function (evt) { 66 | var me = this; 67 | if (!!evt.target.value && evt.keyCode === 13) { 68 | evt.preventDefault(); 69 | me.filterAction(evt.target.value); 70 | } 71 | if (!evt.target.value && evt.keyCode === 8) { 72 | evt.preventDefault(); 73 | me.filterAction(); 74 | } 75 | } 76 | }, 77 | 78 | filterAction: function(filterValue) { 79 | var me = this, val = !!filterValue && filterValue.trim(); 80 | if(!!val) { 81 | me.trigger('filter', { 82 | filterType: me.filterOption, 83 | filterValue: val 84 | }); 85 | } else { 86 | me.trigger('clear-filter', {}) 87 | } 88 | }, 89 | 90 | setFilter: function (fieldName, fieldValue) { 91 | var field = fieldName; 92 | var value = fieldValue; 93 | if (!fieldName || $.trim(fieldName) === '') { 94 | field = 'title'; 95 | value = ''; 96 | } 97 | var me = this; 98 | me.filterOption = field; 99 | $(me.$el.find('.ux-selected-filter').get(0)).html(i18n.get(me.filterOption, {})); 100 | $(me.$el.find('.ux-filter-field').get(0)).val(value); 101 | }, 102 | 103 | render: function () { 104 | var me = this; 105 | if (!this.options.isRendered) { 106 | me.$el.empty(); 107 | me.$el.append(templates.getValue('main', {})); 108 | me.loadDataLink.on('click', function (evt) { 109 | evt.preventDefault(); 110 | me.trigger('load-sample', {}); 111 | }); 112 | me.options.isRendered = true; 113 | } 114 | return this; 115 | }, 116 | 117 | addRows: function (rows) { 118 | var me = this; 119 | var tbody = $(me.$el.find('tbody').get(0)); 120 | tbody.empty(); 121 | paginator.$el.detach(); 122 | me.loadDataLink.detach(); 123 | underscore.each(rows, function (model) { 124 | var row = new TableRowView({ 125 | model: model 126 | }); 127 | row.on('delete', function (data) { 128 | me.trigger('delete', data); 129 | }); 130 | row.on('edit', function (data) { 131 | me.trigger('edit', data); 132 | }); 133 | row.on('movie', function (data) { 134 | me.trigger('movie', data); 135 | }); 136 | tbody.append(row.render().$el); 137 | }); 138 | }, 139 | 140 | setPaginator: function (count) { 141 | var me = this; 142 | paginator.$el.detach(); 143 | me.loadDataLink.detach(); 144 | var uxAdditional = $(me.$el.find('.ux-additional').get(0)); 145 | if (count) { 146 | uxAdditional.prepend(paginator.$el); 147 | } else { 148 | uxAdditional.prepend(me.loadDataLink); 149 | } 150 | } 151 | }); 152 | return new View(); 153 | }); 154 | }()); 155 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/view/movie-page.view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['app/js/templates', 'lib/underscore', 'backbone', 'app/js/tools/alert.view', 'app/js/tools/date', 'app/js/tools/gravatar']; 23 | define(deps, function (templates, _, Backbone, AlertView, DateHelper) { 24 | var View = Backbone.View.extend({ 25 | initialize: function(options){ 26 | this.options = options || {}; 27 | }, 28 | tagName: 'div', 29 | className: 'ux-movie-page', 30 | events: { 31 | 'click .ux-delete-movie': function (evt) { 32 | evt.preventDefault(); 33 | var me = this; 34 | me.trigger('delete', { 35 | model: me.model 36 | }); 37 | }, 38 | 'click .ux-edit-movie': function (evt) { 39 | evt.preventDefault(); 40 | var me = this; 41 | me.trigger('edit', { 42 | model: me.model 43 | }); 44 | }, 45 | 'submit .form-comment': function (evt) { 46 | evt.preventDefault(); 47 | var me = this, 48 | data = evt.target.comment.value, 49 | router = window.BackboneApp.getRouter(), 50 | id = me.model.get('id'); 51 | if(!id) return ; 52 | $.ajax({ 53 | method: "POST", 54 | url: window.ux.ROOT_URL + 'rest/movies/' + id + '/comment', 55 | data: data, 56 | contentType: 'text/plain', 57 | success:function(model) { 58 | me.model.set(model); 59 | me.render(); 60 | }, 61 | error: function(e) { 62 | AlertView.show('Failed', 'Failed to comment (' + e.status + ')', 'danger'); 63 | } 64 | }); 65 | } 66 | }, 67 | render: function () { 68 | var me = this; 69 | me.$el.empty(); 70 | me.$el.append(templates.getValue('movie-page', { 71 | title: me.model.get('title'), 72 | director: me.model.get('director'), 73 | genre: me.model.get('genre'), 74 | rating: me.model.get('rating'), 75 | year: me.model.get('year'), 76 | comments: me.model.get('comments').sort(function(a, b){ 77 | return DateHelper.java2jsDate(a.timestamp) - DateHelper.java2jsDate(b.timestamp); 78 | }), 79 | id: me.model.get('id'), 80 | email: window.ux.auth.get('email'), 81 | author: window.ux.auth.get('username'), 82 | currentYear: new Date().getFullYear() 83 | })); 84 | return me; 85 | } 86 | }); 87 | return View; 88 | }); 89 | }()); 90 | -------------------------------------------------------------------------------- /src/main/webapp/app/js/view/movie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one or more 4 | * contributor license agreements. See the NOTICE file distributed with 5 | * this work for additional information regarding copyright ownership. 6 | * The ASF licenses this file to You under the Apache License, Version 2.0 7 | * (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function () { 20 | 'use strict'; 21 | 22 | var deps = ['app/js/templates', 'lib/underscore', 'backbone', 'app/js/tools/id']; 23 | define(deps, function (templates, _, Backbone) { 24 | var View = Backbone.View.extend({ 25 | initialize: function(options){ 26 | this.options = options || {}; 27 | }, 28 | tagName: 'div', 29 | className: 'modal', 30 | events: { 31 | 'hidden.bs.modal': function (evt) { 32 | evt.preventDefault(); 33 | var me = this; 34 | me.remove(); 35 | }, 36 | 'submit .ux-movie-form': function (evt) { 37 | var me = this; 38 | if (evt.target.checkValidity()) { 39 | evt.preventDefault(); 40 | const model = me.model, 41 | newValues = $(evt.target) 42 | .serializeArray() 43 | .reduce(function (obj, item) { 44 | obj[item.name] = item.value; 45 | return obj; 46 | }, {}); 47 | 48 | model.set(newValues); 49 | me.trigger('save-model', { 50 | model: model 51 | }); 52 | } 53 | } 54 | }, 55 | render: function () { 56 | var me = this; 57 | me.$el.empty(); 58 | me.$el.append(templates.getValue('movie', { 59 | title: me.model.get('title'), 60 | director: me.model.get('director'), 61 | genre: me.model.get('genre'), 62 | rating: me.model.get('rating'), 63 | year: me.model.get('year'), 64 | currentYear: new Date().getFullYear() 65 | })); 66 | return me; 67 | } 68 | }); 69 | return View; 70 | }); 71 | }()); 72 | -------------------------------------------------------------------------------- /src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | 2 | 18 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 19 | <%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %> 20 | 21 | 22 | 23 | Moviefun 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/test/java/org/superbiz/moviefun/MoviesTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

    9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

    11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun; 18 | 19 | import static java.util.Collections.singletonList; 20 | 21 | import java.net.URL; 22 | import java.util.Collection; 23 | import java.util.HashMap; 24 | 25 | import org.apache.cxf.feature.LoggingFeature; 26 | import org.apache.cxf.jaxrs.client.WebClient; 27 | import org.apache.johnzon.jaxrs.JohnzonProvider; 28 | import org.jboss.arquillian.container.test.api.Deployment; 29 | import org.jboss.arquillian.junit.Arquillian; 30 | import org.jboss.arquillian.test.api.ArquillianResource; 31 | import org.jboss.shrinkwrap.api.ShrinkWrap; 32 | import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset; 33 | import org.jboss.shrinkwrap.api.asset.StringAsset; 34 | import org.jboss.shrinkwrap.api.spec.WebArchive; 35 | import org.junit.Ignore; 36 | import org.junit.Test; 37 | import org.junit.runner.RunWith; 38 | import org.superbiz.moviefun.rest.ApplicationConfig; 39 | import org.superbiz.moviefun.rest.LoadDataResource; 40 | import org.superbiz.moviefun.rest.MoviesMPJWTConfigurationProvider; 41 | import org.superbiz.moviefun.rest.MoviesResource; 42 | import org.superbiz.moviefun.utils.TokenUtil; 43 | import org.superbiz.rest.GreetingResource; 44 | 45 | @Ignore 46 | @RunWith(Arquillian.class) 47 | public class MoviesTest { 48 | 49 | @Deployment(testable = false) 50 | public static WebArchive createDeployment() { 51 | final WebArchive webArchive = ShrinkWrap.create(WebArchive.class, "test.war") 52 | .addClasses(Movie.class, MoviesBean.class, MoviesTest.class, LoadDataResource.class) 53 | .addClasses(MoviesResource.class, GreetingResource.class, ApplicationConfig.class) 54 | .addClass(STSResource.class) 55 | .addClass(MoviesMPJWTConfigurationProvider.class) 56 | .addAsWebInfResource(new StringAsset(""), "beans.xml") 57 | .addAsResource(new ClassLoaderAsset("META-INF/persistence.xml"), "META-INF/persistence.xml"); 58 | 59 | System.out.println(webArchive.toString(true)); 60 | 61 | return webArchive; 62 | } 63 | 64 | @ArquillianResource 65 | private URL base; 66 | 67 | @Test 68 | public void sthg() throws Exception { 69 | 70 | final WebClient webClient = WebClient 71 | .create(base.toExternalForm(), singletonList(new JohnzonProvider<>()), singletonList(new LoggingFeature()), null); 72 | 73 | webClient 74 | .reset() 75 | .path("/rest/greeting/") 76 | .get(String.class); 77 | 78 | final Collection movies = webClient 79 | .reset() 80 | .path("/rest/movies/") 81 | .header("Authorization", "Bearer " + token()) 82 | .getCollection(Movie.class); 83 | 84 | System.out.println(movies); 85 | } 86 | 87 | private String token() throws Exception { 88 | return TokenUtil.generateTokenString("/Token1.json", null, new HashMap<>()); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/org/superbiz/moviefun/STSResource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

    9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

    11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun; 18 | 19 | import com.nimbusds.jwt.JWTClaimsSet; 20 | import org.apache.commons.lang.StringUtils; 21 | import org.superbiz.moviefun.rest.MoviesMPJWTConfigurationProvider; 22 | import org.superbiz.moviefun.utils.TokenUtil; 23 | 24 | import javax.ws.rs.Consumes; 25 | import javax.ws.rs.POST; 26 | import javax.ws.rs.Path; 27 | import javax.ws.rs.Produces; 28 | import javax.ws.rs.core.MediaType; 29 | import javax.ws.rs.core.MultivaluedMap; 30 | import javax.ws.rs.core.Response; 31 | import java.util.ArrayList; 32 | import java.util.Arrays; 33 | import java.util.HashMap; 34 | import java.util.List; 35 | import java.util.UUID; 36 | 37 | @Path("token") 38 | @Produces(MediaType.APPLICATION_JSON) 39 | /** 40 | * This is a mock implementation for delivering JWT. It is meant to be only for testing purpose and should in reality be 41 | * an actual STS IDP such as Tribestream API Gateway or other identity providers capable of producing JWT 42 | */ 43 | public class STSResource { 44 | 45 | @POST 46 | @Produces(MediaType.APPLICATION_JSON) 47 | @Consumes(MediaType.APPLICATION_FORM_URLENCODED) 48 | public Response authenticate(final MultivaluedMap formParameters) { 49 | 50 | final String clientId = nullSafeGetFormParameter("client_id", formParameters); 51 | final String clientSecret = nullSafeGetFormParameter("client_secret", formParameters); 52 | final String code = nullSafeGetFormParameter("code", formParameters); 53 | final String grantType = nullSafeGetFormParameter("grant_type", formParameters); 54 | final String redirectUri = nullSafeGetFormParameter("redirect_uri", formParameters); 55 | final String refreshToken = nullSafeGetFormParameter("refresh_token", formParameters); 56 | final String username = nullSafeGetFormParameter("username", formParameters); 57 | final String password = nullSafeGetFormParameter("password", formParameters); 58 | final String scope = nullSafeGetFormParameter("scope", formParameters); 59 | 60 | // validate incoming parameters 61 | { 62 | final TokenErrorResponse tokenErrorResponse = validateGrantType(grantType); 63 | if (tokenErrorResponse != null) { 64 | return Response.status(Response.Status.BAD_REQUEST).entity(tokenErrorResponse).build(); 65 | } 66 | } 67 | 68 | { 69 | final TokenErrorResponse tokenErrorResponse = validateAttributes(grantType, refreshToken, 70 | username, password, clientId, clientSecret, code, scope, redirectUri); 71 | if (tokenErrorResponse != null) { 72 | return Response.status(Response.Status.BAD_REQUEST).entity(tokenErrorResponse).build(); 73 | } 74 | } 75 | 76 | final List scopes = new ArrayList<>(); 77 | final int currentTimeInSecs = TokenUtil.currentTimeInSecs(); 78 | final long expiresIn = 300; // still in seconds 79 | final long expires = currentTimeInSecs + expiresIn; 80 | 81 | // validate grants (especially the supported grants) 82 | if ("password".equalsIgnoreCase(grantType)) { 83 | 84 | // validate the user and client credentials 85 | if ("hacker".equals(username)) { 86 | return Response.status(Response.Status.BAD_REQUEST).entity(new TokenErrorResponse( 87 | "bad_credentials", 88 | "Invalid provided credentials" 89 | )).build(); 90 | 91 | } else if ("admin".equals(username)) { 92 | scopes.add("create"); 93 | scopes.add("update"); 94 | scopes.add("delete"); 95 | 96 | } else { 97 | scopes.add(username); 98 | 99 | } 100 | 101 | final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() 102 | .jwtID(UUID.randomUUID().toString()) 103 | .claim("groups", scopes) 104 | .claim("username", username) 105 | .claim("email", username + "@superbiz.org") 106 | .subject(username) 107 | .audience("mp-jwt-moviefun") 108 | .issuer(MoviesMPJWTConfigurationProvider.ISSUED_BY) 109 | .build(); 110 | 111 | final String accessToken; 112 | try { 113 | accessToken = TokenUtil.generateTokenString(claimsSet.toJSONObject(), null, new HashMap() {{ 114 | put("exp", expires); 115 | }}); 116 | 117 | return Response.ok().entity(new TokenResponse( 118 | accessToken, 119 | "bearer", 120 | Math.round(expires - TokenUtil.currentTimeInSecs()), 121 | null, // refresh not supported 122 | StringUtils.join(scopes, ' '))).build(); 123 | 124 | } catch (final Exception e) { 125 | return Response.status(Response.Status.BAD_REQUEST).entity(new TokenErrorResponse( 126 | "invalid_grant", "An issue occurred while creating the token." // should we disclose the actual error? 127 | )).build(); 128 | } 129 | 130 | 131 | } else { 132 | return Response.status(Response.Status.BAD_REQUEST).entity(new TokenErrorResponse( 133 | "unsupported_grant_type", grantType + " is not a supported grant type. Only password is supported." 134 | )).build(); 135 | } 136 | 137 | } 138 | 139 | private TokenErrorResponse validateAttributes( 140 | final String grantType, final String refreshToken, 141 | final String username, final String password, 142 | final String clientId, final String clientSecret, 143 | final String code, final String scope, 144 | final String redirectUri) { 145 | 146 | if ("password".equals(grantType)) { 147 | if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { 148 | new TokenErrorResponse( 149 | "missing_credentials", 150 | "username and password are required to authenticate a user with the password grant type."); 151 | } 152 | } 153 | 154 | // more to do if needed 155 | 156 | return null; 157 | } 158 | 159 | private TokenErrorResponse validateGrantType(final String grantType) { 160 | final List grants = Arrays.asList("password", "client_credentials", "refresh_token", "authorization_code"); 161 | if (StringUtils.isEmpty(grantType)) { 162 | new TokenErrorResponse("grant_type_required", "grant_type is a required parameter"); 163 | } 164 | if (!grants.contains(grantType)) { 165 | new TokenErrorResponse( 166 | "unsupported_grant_type", 167 | grantType + " is not supported. Only supporting " + StringUtils.join(grants, ", ")); 168 | } 169 | return null; 170 | } 171 | 172 | private static String nullSafeGetFormParameter(String parameterName, MultivaluedMap formParameters) { 173 | List params = formParameters.get(parameterName); 174 | return params == null || params.isEmpty() ? null : params.get(0); 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /src/test/java/org/superbiz/moviefun/TokenErrorResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

    9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

    11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun; 18 | 19 | import org.apache.johnzon.mapper.JohnzonProperty; 20 | 21 | import java.beans.ConstructorProperties; 22 | import java.io.Serializable; 23 | 24 | public class TokenErrorResponse implements Serializable { 25 | @JohnzonProperty("error") 26 | private final String error; 27 | 28 | @JohnzonProperty("error_description") 29 | private final String errorDescription; 30 | 31 | @ConstructorProperties({"error", "error_description"}) 32 | public TokenErrorResponse(final String error, final String errorDescription) { 33 | this.error = error; 34 | this.errorDescription = errorDescription; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "TokenErrorResponse{" + 40 | "error='" + error + '\'' + 41 | ", errorDescription='" + errorDescription + '\'' + 42 | '}'; 43 | } 44 | 45 | public String getError() { 46 | return error; 47 | } 48 | 49 | public String getErrorDescription() { 50 | return errorDescription; 51 | } 52 | } -------------------------------------------------------------------------------- /src/test/java/org/superbiz/moviefun/TokenResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

    9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

    11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.moviefun; 18 | 19 | import org.apache.johnzon.mapper.JohnzonProperty; 20 | 21 | import java.beans.ConstructorProperties; 22 | import java.io.Serializable; 23 | import java.util.UUID; 24 | 25 | /** 26 | * Bean representation of a Access Token response. See this 28 | * section of the spec for more info. 29 | */ 30 | 31 | public class TokenResponse implements Serializable { 32 | @JohnzonProperty("access_token") 33 | private final String accessToken; 34 | 35 | @JohnzonProperty("token_type") 36 | private final String tokenType; 37 | 38 | @JohnzonProperty("expires_in") 39 | private final long expiresIn; 40 | 41 | @JohnzonProperty("refresh_token") 42 | private final String refreshToken; 43 | 44 | @JohnzonProperty("id_token") 45 | private final String idToken; 46 | 47 | private final String scope; 48 | 49 | @ConstructorProperties({"accessToken", "tokenType", "expiresIn", "refreshToken", "scope"}) 50 | public TokenResponse(final String accessToken, final String tokenType, final long expiresIn, final String refreshToken, final String scope) { 51 | this.accessToken = accessToken; 52 | this.tokenType = tokenType; 53 | this.expiresIn = expiresIn; 54 | this.refreshToken = refreshToken; 55 | this.scope = scope; 56 | this.idToken = UUID.randomUUID().toString(); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "TokenResponse{" + 62 | "accessToken='" + accessToken + '\'' + 63 | ", tokenType='" + tokenType + '\'' + 64 | ", expiresIn=" + expiresIn + 65 | ", refreshToken='" + refreshToken + '\'' + 66 | ", idToken='" + idToken + '\'' + 67 | ", scope='" + scope + '\'' + 68 | '}'; 69 | } 70 | 71 | public String getAccessToken() { 72 | return accessToken; 73 | } 74 | 75 | public long getExpiresIn() { 76 | return expiresIn; 77 | } 78 | 79 | public String getIdToken() { 80 | return idToken; 81 | } 82 | 83 | public String getRefreshToken() { 84 | return refreshToken; 85 | } 86 | 87 | public String getScope() { 88 | return scope; 89 | } 90 | 91 | public String getTokenType() { 92 | return tokenType; 93 | } 94 | } -------------------------------------------------------------------------------- /src/test/java/org/superbiz/rest/GreetingResourceTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | *

    9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | *

    11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.superbiz.rest; 18 | 19 | import org.apache.cxf.jaxrs.client.WebClient; 20 | import org.apache.openejb.jee.WebApp; 21 | import org.apache.openejb.junit.ApplicationComposer; 22 | import org.apache.openejb.testing.Classes; 23 | import org.apache.openejb.testing.EnableServices; 24 | import org.apache.openejb.testing.Module; 25 | import org.junit.Ignore; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.superbiz.moviefun.LoadBalancerRegisterService; 29 | 30 | import javax.ws.rs.core.MediaType; 31 | import java.io.IOException; 32 | 33 | import static org.junit.Assert.assertEquals; 34 | 35 | @EnableServices(value = "jaxrs", httpDebug = true) 36 | @RunWith(ApplicationComposer.class) 37 | public class GreetingResourceTest { 38 | 39 | @Module 40 | @Classes(GreetingResource.class) 41 | public WebApp app() { 42 | return new WebApp().contextRoot("test"); 43 | } 44 | 45 | @Test 46 | public void get() throws IOException { 47 | final String message = WebClient.create("http://localhost:4204") 48 | .path("/test/greeting/") 49 | .accept(MediaType.APPLICATION_JSON_TYPE) 50 | .get(String.class); 51 | assertEquals("Hi Microprofile JWT!", message); 52 | } 53 | 54 | @Test 55 | public void post() throws IOException { 56 | final String message = WebClient.create("http://localhost:4204") 57 | .path("/test/greeting/") 58 | .type(MediaType.APPLICATION_JSON_TYPE) 59 | .accept(MediaType.APPLICATION_JSON_TYPE) 60 | .post("Hi REST!", String.class); 61 | assertEquals("hi rest!", message); 62 | } 63 | 64 | @Test 65 | // @Ignore("To be executed manually for the moment") 66 | public void register() { 67 | final LoadBalancerRegisterService service = new LoadBalancerRegisterService(); 68 | service.setHostUrl("http://localhost:8080"); 69 | service.setServerUrl("http://localhost:8182"); 70 | service.setConnectionId("movies-api-connection"); 71 | service.setRegisterEndpoint("/api/http/{connectionId}/hosts/register"); 72 | 73 | service.setSignaturesKeyId("my-key-id"); 74 | service.setSignaturesKey("YmQwYzE4MTg4ZjJjMWVkNWJhOTE3Yzc5MTRlYzI1ZjMxYTZiZDdlMDYxZWRjMDgx"); 75 | service.setSignaturesAlgorithm("hmac-sha256"); 76 | service.setSignaturesSignedHeaders("(request-target) date digest"); 77 | 78 | service.setup(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/application-client.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/Token1.json: -------------------------------------------------------------------------------- 1 | { 2 | "iss": "/oauth2/token", 3 | "jti": "a-123", 4 | "sub": "24400320", 5 | "upn": "jdoe@example.com", 6 | "preferred_username": "jdoe", 7 | "aud": "s6BhdRkqt3", 8 | "exp": 1311281970, 9 | "iat": 1311280970, 10 | "auth_time": 1311280969, 11 | "roles": [ 12 | "Echoer" 13 | ], 14 | "groups": [ 15 | "Echoer", 16 | "Tester", 17 | "group1", 18 | "group2" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/Token2.json: -------------------------------------------------------------------------------- 1 | { 2 | "iss": "https://server.example.com", 3 | "jti": "a-123.2", 4 | "sub": "24400320#2", 5 | "upn": "jdoe2@example.com", 6 | "preferred_username": "jdoe", 7 | "aud": "s6BhdRkqt3.2", 8 | "exp": 1311281970, 9 | "iat": 1311280970, 10 | "auth_time": 1311280969, 11 | "groups": ["Echoer2", "Tester", "Token2Role", "group1.2", "group2.2"] 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 22 | 23 | 24 | 25 | -1 26 | -1 27 | microprofile 28 | target/apache-tomee-microprofile 29 | target/arquillian-test-working-dir 30 | 31 | 32 | --------------------------------------------------------------------------------