From 46e35b337b77ffb497726c85036db7e8a7e06013 Mon Sep 17 00:00:00 2001 From: Jaideep Palit Date: Fri, 12 Nov 2021 22:56:49 +0530 Subject: [PATCH 1/7] feat(auth): Added support for Authentication: Machine Token Only Approach with OIDC provider Signed-off-by: Jaideep Palit --- .../sw360/datahandler/db/UserRepository.java | 11 ++ .../org/eclipse/sw360/users/UserHandler.java | 7 + .../sw360/users/db/UserDatabaseHandler.java | 4 + .../sw360/portal/common/PortalConstants.java | 3 + .../sw360/portal/common/PortletUtils.java | 50 ++++++ .../portal/portlets/admin/UserPortlet.java | 12 +- .../tags/DisplayMapOfOidcClientAndAcess.java | 69 +++++++++ .../main/resources/META-INF/customTags.tld | 11 ++ .../resources/html/admin/user/detail.jsp | 4 + .../resources/html/admin/user/edit.jsp | 3 + .../html/utils/includes/editOauthClientId.jsp | 144 ++++++++++++++++++ .../resources/content/Language.properties | 5 + .../resources/content/Language_ja.properties | 5 + .../resources/content/Language_vi.properties | 5 + .../datahandler/common/ThriftEnumUtils.java | 7 + .../src/main/thrift/users.thrift | 16 ++ rest/resource-server/pom.xml | 5 + .../resourceserver/Sw360ResourceServer.java | 6 + .../core/JacksonCustomizations.java | 4 +- .../core/SimpleAuthenticationEntryPoint.java | 47 ++++++ .../security/ResourceServerConfiguration.java | 6 +- .../ApiTokenAuthenticationFilter.java | 26 ++++ .../ApiTokenAuthenticationProvider.java | 87 +++++++++-- .../security/jwksvalidation/JWTValidator.java | 49 ++++++ .../resourceserver/user/Sw360UserService.java | 9 ++ 25 files changed, 573 insertions(+), 22 deletions(-) create mode 100644 frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/DisplayMapOfOidcClientAndAcess.java create mode 100644 frontend/sw360-portlet/src/main/resources/META-INF/resources/html/utils/includes/editOauthClientId.jsp create mode 100644 rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/SimpleAuthenticationEntryPoint.java create mode 100644 rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/jwksvalidation/JWTValidator.java diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/UserRepository.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/UserRepository.java index 4e6497219e..0a3c2712b4 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/UserRepository.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/UserRepository.java @@ -57,6 +57,11 @@ public class UserRepository extends SummaryAwareRepository { " emit(doc.restApiTokens[i].token, doc._id)" + " }" + "}"; + private static final String BYOIDCCLIENTID = "function(doc) { if (doc.type == 'user') " + + " for (var i in doc.oidcClientInfos) {" + + " emit(i, doc._id)" + + " }" + + "}"; private static final String BYEMAIL = "function(doc) { " + " if (doc.type == 'user') {" + " emit(doc.email, doc._id); " + @@ -87,6 +92,7 @@ public UserRepository(DatabaseConnectorCloudant databaseConnector) { views.put("all", createMapReduce(ALL, null)); views.put("byExternalId", createMapReduce(BYEXTERNALID, null)); views.put("byApiToken", createMapReduce(BYAPITOKEN, null)); + views.put("byOidcClientId", createMapReduce(BYOIDCCLIENTID, null)); views.put("byEmail", createMapReduce(BYEMAIL, null)); views.put("userDepartments", createMapReduce(USERS_ALL_DEPARTMENT_VIEW, null)); views.put("userEmails", createMapReduce(USERS_ALL_EMAIL_VIEW, null)); @@ -243,4 +249,9 @@ private Map> queryViewWithPagination(PaginationData p result.put(pageData, users); return result; } + + public User getByOidcClientId(String clientId) { + final Set userIds = queryForIdsAsValue("byOidcClientId", clientId); + return getUserFromIds(userIds); + } } diff --git a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java index 5e605e8d74..49d687c738 100644 --- a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java +++ b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/UserHandler.java @@ -83,6 +83,13 @@ public User getByApiToken(String token) throws TException { return db.getByApiToken(token); } + + @Override + public User getByOidcClientId(String clientId) throws TException { + assertNotEmpty(clientId); + return db.getByOidcClientId(clientId); + } + @Override public List searchUsers(String searchText) { return db.searchUsers(searchText); diff --git a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java index 0170728561..197be5aa3d 100644 --- a/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java +++ b/backend/src/src-users/src/main/java/org/eclipse/sw360/users/db/UserDatabaseHandler.java @@ -130,4 +130,8 @@ public List search(String text, Map> subQueryRestricti public Map> getUsersWithPagination(PaginationData pageData) { return repository.getUsersWithPagination(pageData); } + + public User getByOidcClientId(String clientId) { + return repository.getByOidcClientId(clientId); + } } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java index 1f12fd8a5a..bf5b6df2b3 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java @@ -640,6 +640,9 @@ public class PortalConstants { public static final String USERS_PRESENT_IN_COUCH_DB = "usersPresentInCouchDb"; public static final String USERS_ABSENT_IN_COUCH_DB = "usersAbsentInCouchDb"; public static final String USER_OBJ = "userObj"; + public static final String USER_CLIENT_ID_KEY = "userClientIdKey"; + public static final String USER_CLIENT_ID_ACCESS_VALUE = "userClientIdAccessValue"; + public static final String USER_CLIENT_ID_NAME_VALUE = "userClientIdNameValue"; // Rest API constants public static final UserGroup API_WRITE_ACCESS_USERGROUP; diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortletUtils.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortletUtils.java index c5318341d9..f80f268a59 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortletUtils.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortletUtils.java @@ -39,6 +39,8 @@ import org.eclipse.sw360.datahandler.thrift.projects.ProjectState; import org.eclipse.sw360.datahandler.thrift.projects.ProjectType; import org.eclipse.sw360.datahandler.thrift.users.User; +import org.eclipse.sw360.datahandler.thrift.users.UserAccess; +import org.eclipse.sw360.datahandler.thrift.users.ClientMetadata; import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.eclipse.sw360.datahandler.thrift.vulnerabilities.VulnerabilityDTO; import org.eclipse.sw360.portal.common.customfields.CustomField; @@ -389,6 +391,36 @@ public static Map> getCustomMapFromRequest(PortletRequest req return customMap; } + public static Map> getCustomMapFromOidcClientDetails(PortletRequest request, + String mapKeyClientId, String mapValueName, String mapValueAccess) { + Map> customMap = new HashMap<>(); + Enumeration parameterNames = request.getParameterNames(); + List keyAndValueParameterIds = Collections.list(parameterNames).stream() + .filter(p -> p.startsWith(mapKeyClientId)).map(s -> s.replace(mapKeyClientId, "")) + .collect(Collectors.toList()); + for (String parameterId : keyAndValueParameterIds) { + String key = request.getParameter(mapKeyClientId + parameterId); + if (isNullEmptyOrWhitespace(key)) { + LOGGER.error("Empty map key found"); + } else { + String valueName = request.getParameter(mapValueName + parameterId); + String valueAccess = request.getParameter(mapValueAccess + parameterId); + if (valueName == null) { + valueName = ""; + } + if (valueAccess == null) { + valueAccess = "READ"; + } + if (!customMap.containsKey(key)) { + customMap.put(key, new ArrayList<>()); + } + customMap.get(key).add(valueName); + customMap.get(key).add(valueAccess); + } + } + return customMap; + } + public static Map getMapWithJoinedValueFromRequest(PortletRequest request, String key, String value) { Map> customMap = getCustomMapFromRequest(request, key, value); return customMap.entrySet().stream() @@ -439,6 +471,24 @@ public static Map> getSecondaryDepartmentAndRolesMapFromR return null; } + public static Map getOidcClientMapFromRequest(PortletRequest request) { + Map> customMap = getCustomMapFromOidcClientDetails(request, + PortalConstants.USER_CLIENT_ID_KEY, PortalConstants.USER_CLIENT_ID_NAME_VALUE, + PortalConstants.USER_CLIENT_ID_ACCESS_VALUE); + if (!CommonUtils.isNullOrEmptyMap(customMap)) { + return customMap.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey(), entry -> { + String userAccess = entry.getValue().get(1); + ClientMetadata clientMetadata = new ClientMetadata(entry.getValue().get(0), UserAccess.READ); + if (CommonUtils.isNotNullEmptyOrWhitespace(userAccess)) { + return clientMetadata.setAccess(UserAccess.valueOf(userAccess)); + } + return clientMetadata; + })); + } + + return null; + } + public static Map getAdditionalDataMapFromRequest(PortletRequest request) { return getMapFromRequest(request, PortalConstants.ADDITIONAL_DATA_KEY, PortalConstants.ADDITIONAL_DATA_VALUE); } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/UserPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/UserPortlet.java index c1a79f9a1d..425ee12e0f 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/UserPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/UserPortlet.java @@ -37,6 +37,8 @@ import org.eclipse.sw360.datahandler.common.ThriftEnumUtils; import org.eclipse.sw360.datahandler.couchdb.lucene.LuceneAwareDatabaseConnector; import org.eclipse.sw360.datahandler.thrift.SW360Exception; +import org.eclipse.sw360.datahandler.thrift.users.ClientMetadata; +import org.eclipse.sw360.datahandler.thrift.users.UserAccess; import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.eclipse.sw360.datahandler.thrift.users.UserService; import org.eclipse.sw360.portal.common.ErrorMessages; @@ -338,13 +340,15 @@ private boolean updateUserObjectFromRequest(ActionRequest request, ActionRespons Map> secondaryDepartmentAndRolesMapFromRequest = PortletUtils .getSecondaryDepartmentAndRolesMapFromRequest(request, departmentFromReq); - + Map userAccessFromRequest = PortletUtils + .getOidcClientMapFromRequest(request); String originalEmail = CommonUtils.nullToEmptyString(userByEmailFromCouchDB.getEmail()); org.eclipse.sw360.datahandler.thrift.users.User updatedUserForDisplay = userByEmailFromCouchDB.deepCopy(); updatedUserForDisplay.setGivenname(givenNameFromReq).setLastname(lastNameFromReq) .setDepartment(departmentFromReq).setExternalid(externalIdFromReq) .setUserGroup(UserGroup.valueOf(primaryRoleFromReq)).setPrimaryRoles(null) - .setSecondaryDepartmentsAndRoles(secondaryDepartmentAndRolesMapFromRequest); + .setSecondaryDepartmentsAndRoles(secondaryDepartmentAndRolesMapFromRequest) + .setOidcClientInfos(userAccessFromRequest); request.setAttribute(PortalConstants.USER_OBJ, updatedUserForDisplay); if (isLiferayUserNew) { if (!emailFromReq.equals(originalEmail)) { @@ -451,6 +455,7 @@ private boolean updateUserObjectFromRequest(ActionRequest request, ActionRespons org.eclipse.sw360.datahandler.thrift.users.User newlyCreatedUser = userClient.getByEmail(emailFromReq); newlyCreatedUser.setSecondaryDepartmentsAndRoles(secondaryDepartmentAndRolesMapFromRequest) + .setOidcClientInfos(userAccessFromRequest) .setFullname(liferayCreatedOrUpdated.getFullName()) .setPrimaryRoles(null).setUserGroup(userGroupFromString(primaryRoleFromReq)); userClient.updateUser(newlyCreatedUser); @@ -466,7 +471,8 @@ private boolean updateUserObjectFromRequest(ActionRequest request, ActionRespons .setDepartment(departmentFromReq).setExternalid(externalIdFromReq) .setFullname(liferayCreatedOrUpdated.getFullName()).setUserGroup(UserGroup.valueOf(primaryRoleFromReq)) .setPrimaryRoles(null); - userByEmailFromCouchDB.setSecondaryDepartmentsAndRoles(secondaryDepartmentAndRolesMapFromRequest); + userByEmailFromCouchDB.setSecondaryDepartmentsAndRoles(secondaryDepartmentAndRolesMapFromRequest) + .setOidcClientInfos(userAccessFromRequest); if (isCouchDBUserNew || userByEmailFromCouchDB.getId() == null) { userClient.addUser(userByEmailFromCouchDB.setEmail(emailFromReq)); } else { diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/DisplayMapOfOidcClientAndAcess.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/DisplayMapOfOidcClientAndAcess.java new file mode 100644 index 0000000000..0aa900cd5f --- /dev/null +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/tags/DisplayMapOfOidcClientAndAcess.java @@ -0,0 +1,69 @@ +/* + * Copyright Siemens AG, 2021. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.sw360.portal.tags; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +import org.apache.commons.lang.StringEscapeUtils; +import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.datahandler.common.ThriftEnumUtils; +import org.eclipse.sw360.datahandler.thrift.users.ClientMetadata; +import org.eclipse.sw360.datahandler.thrift.users.UserAccess; +import org.eclipse.sw360.datahandler.thrift.users.UserGroup; + +/** + * This displays a map of secondary groups and roles + * + * @author jaideep.palit@siemens.com + */ +public class DisplayMapOfOidcClientAndAcess extends SimpleTagSupport { + + private Map value; + + public void setValue(Map value) { + this.value = value; + } + + public void doTag() throws JspException, IOException { + Map fullValue = (value == null) ? new HashMap<>() : value; + + if (!CommonUtils.isNullOrEmptyMap(fullValue)) { + String result = getMapAsString(new TreeMap(fullValue)); + getJspContext().getOut().print(result); + } + } + + public static String getMapAsString(Map map) { + StringBuilder sb = new StringBuilder(); + sb.append("
    "); + map.forEach((entryKey, entryValue) -> sb.append(getclientIdAndMetadataString(entryKey, entryValue))); + sb.append("
"); + return sb.toString(); + } + + private static String getclientIdAndMetadataString(String clientId, ClientMetadata clientMetadata) { + StringBuilder sb = new StringBuilder(); + sb.append("
  • ") + .append(StringEscapeUtils.escapeXml(clientMetadata.getName())) + .append(" -> ") + .append(StringEscapeUtils.escapeXml(clientId)) + .append(" -> ") + .append(ThriftEnumUtils.enumToString(clientMetadata.getAccess())) + .append("
  • "); + return sb.toString(); + } +} diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/customTags.tld b/frontend/sw360-portlet/src/main/resources/META-INF/customTags.tld index be4350eb97..7d686cb68c 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/customTags.tld +++ b/frontend/sw360-portlet/src/main/resources/META-INF/customTags.tld @@ -140,6 +140,17 @@ true + + DisplayMapOfOidcClientAndAcess + org.eclipse.sw360.portal.tags.DisplayMapOfOidcClientAndAcess + empty + + value + true + java.util.Map + true + + DisplayMapOfEmailSets org.eclipse.sw360.portal.tags.DisplayMapOfEmailStringSets diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/user/detail.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/user/detail.jsp index 44677bb9cc..d88293d651 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/user/detail.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/user/detail.jsp @@ -115,6 +115,10 @@ : + + : + + diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/user/edit.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/user/edit.jsp index 3bfc59809b..e112f587b7 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/user/edit.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/user/edit.jsp @@ -32,6 +32,7 @@ + @@ -192,6 +193,8 @@ <%@include file="/html/utils/includes/editSecondaryDepartmentAndRoles.jsp"%> +
    + <%@include file="/html/utils/includes/editOauthClientId.jsp"%> diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/utils/includes/editOauthClientId.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/utils/includes/editOauthClientId.jsp new file mode 100644 index 0000000000..4dbc61442e --- /dev/null +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/utils/includes/editOauthClientId.jsp @@ -0,0 +1,144 @@ +<%-- + ~ Copyright Siemens AG, 2017-2019. Part of the SW360 Portal Project. + ~ + ~ This program and the accompanying materials are made + ~ available under the terms of the Eclipse Public License 2.0 + ~ which is available at https://www.eclipse.org/legal/epl-2.0/ + ~ + ~ SPDX-License-Identifier: EPL-2.0 +--%> +<%@ page import="org.eclipse.sw360.portal.common.PortalConstants"%> +<%@ page import="org.eclipse.sw360.datahandler.thrift.users.UserAccess"%> + + + + + + + +
    + + + +
    + +
    + + diff --git a/frontend/sw360-portlet/src/main/resources/content/Language.properties b/frontend/sw360-portlet/src/main/resources/content/Language.properties index cc1b3ac71d..6453189f34 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language.properties @@ -214,6 +214,7 @@ clearing.team.will.confirm.on.the.agreed.clearing.date=Clearing team will confir cli.attachment.evaluation.completed=CLI attachment evaluation completed. cli.attachment.not.found.in.the.release=CLI attachment not found in the release cli.count=CLI count +click.to.add.oidc.client=Click to add OIDC client click.to.add.releases=Click to add Releases click.to.add.row.to=Click to add row to click.to.add.row.to.additional.data=Click to add row to Additional Data @@ -500,6 +501,8 @@ enter.mailing.list.url=Enter Mailing List Url enter.material.index.number=Enter material index number enter.name=Enter Name enter.obligation.comment=Enter obligation comment +enter.oidc.client.id=Enter OIDC Client Id +enter.oidc.client.name=Enter OIDC Client Name enter.one.word.tag=Enter one word tag enter.owner.accounting.unit=Enter Owner Accounting Unit enter.owner.billing.group=Enter Owner Billing Group @@ -890,6 +893,7 @@ obligation.status.is.saved.successfully.refresh.the.page.to.see.the.updated.stat ok=OK on.hold=On Hold obligations.view=Obligations View +oidc.client=OIDC Client on=on only.administrators.can.edit.a.closed.project=Only administrators can edit a closed project. only.admin.users.can.delete.obligations=Only admin users can delete obliations! @@ -1048,6 +1052,7 @@ quick.filter=Quick Filter rate=Rate rational.team.concert=Rational Team Concert read.access=Read Access +read.and.write.access=Read and Write Access recent.components=Recent Components recent.releases=Recent Releases re.check.connection=Re-Check connection diff --git a/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties b/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties index 0984446bf9..4ff027f996 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties @@ -214,6 +214,7 @@ clearing.team.will.confirm.on.the.agreed.clearing.date=クリアリングチー cli.attachment.evaluation.completed=CLI attachment evaluation completed. cli.attachment.not.found.in.the.release=CLI attachment not found in the release cli.count=CLI count +click.to.add.oidc.client=Click to add OIDC client click.to.add.releases=クリックしてリリースを追加 click.to.add.row.to=クリックして行を追加 click.to.add.row.to.additional.data=クリックして追加データを行に追加 @@ -500,6 +501,8 @@ enter.mailing.list.url=メーリングリストのURLを入力 enter.material.index.number=部品指標番号(Material index number)を入力 enter.name=名前を入力 enter.obligation.comment=オブリゲーションコメントを入力 +enter.oidc.client.id=Enter OIDC Client Id +enter.oidc.client.name=Enter OIDC Client Name enter.one.word.tag=単語タグを入力 enter.owner.accounting.unit=所有者の会計単位を入力 enter.owner.billing.group=会計グループオーナーを入力 @@ -889,6 +892,7 @@ on.hold=保留 obligations.are.not.present.in.accepted.cli.xml.file=オブリゲーションを入れた CLI xmlファイルが存在しません。 obligation.status.is.saved.successfully.refresh.the.page.to.see.the.updated.status=オブリゲーションステータスは正常に保存されています。 obligations.view=オブリゲーションの表示 +oidc.client=OIDC Client ok=OK on=on only.administrators.can.edit.a.closed.project=閉じたプロジェクトを編集できるのは管理者だけです。 @@ -1048,6 +1052,7 @@ quick.filter=クイックフィルター rate=レート rational.team.concert=Rational チーム協力 read.access=読み取りアクセス +read.and.write.access=Read and Write Access recent.components=最近の成分 recent.releases=最近のリリース re.check.connection=接続の再確認 diff --git a/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties b/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties index c34ab58dd4..5d5650e140 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties @@ -215,6 +215,7 @@ clearing.team.will.confirm.on.the.agreed.clearing.date=Clearing team will confir cli.attachment.evaluation.completed=CLI attachment evaluation completed. cli.attachment.not.found.in.the.release=CLI attachment not found in the release cli.count=CLI count +click.to.add.oidc.client=Click to add OIDC client click.to.add.releases=Nhấn vào đây để thêm Phát hành click.to.add.row.to=Nhấn vào đây để thêm hàng vào click.to.add.row.to.additional.data=Nhấn vào đây để thêm hàng vào Dữ liệu bổ sung @@ -503,6 +504,8 @@ enter.mailing.list.url=Nhập Url danh sách gửi thư enter.material.index.number=Nhập số chỉ mục tài liệu enter.name=Nhập tên enter.obligation.comment=Nhập nghĩa vụ bình luận +enter.oidc.client.id=Enter OIDC Client Id +enter.oidc.client.name=Enter OIDC Client Name enter.one.word.tag=Nhập một thẻ enter.owner.accounting.unit=Nhập đơn vị kế toán enter.owner.billing.group=Nhập nhóm thanh toán @@ -891,6 +894,7 @@ on.hold=On Hold obligations.are.not.present.in.accepted.cli.xml.file=Nghĩa vụ không có trong tệp xml CLI được chấp nhận. obligation.status.is.saved.successfully.refresh.the.page.to.see.the.updated.status=Trạng thái nghĩa vụ được lưu thành công, Làm mới trang để xem trạng thái cập nhật obligations.view=Xem nghĩa vụ +oidc.client=OIDC Client ok=OK on=on only.administrators.can.edit.a.closed.project=Chỉ quản trị viên có thể dự án đã đóng @@ -1046,6 +1050,7 @@ quick.filter=Bộ lọc nhanh rate=Tỷ lệ rational.team.concert=Buổi hòa nhạc nhóm hợp lý read.access=Đọc quyền truy cập +read.and.write.access=Read and Write Access recent.components=Các thành phần gần đây recent.releases=Các bản phát hành gần đây re.check.connection=Kiểm tra lại kết nối diff --git a/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/common/ThriftEnumUtils.java b/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/common/ThriftEnumUtils.java index 3ede2b1497..a118a18a64 100644 --- a/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/common/ThriftEnumUtils.java +++ b/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/common/ThriftEnumUtils.java @@ -22,6 +22,7 @@ import org.eclipse.sw360.datahandler.thrift.licenses.ObligationType; import org.eclipse.sw360.datahandler.thrift.moderation.DocumentType; import org.eclipse.sw360.datahandler.thrift.projects.*; +import org.eclipse.sw360.datahandler.thrift.users.UserAccess; import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.eclipse.sw360.datahandler.thrift.vulnerabilities.VulnerabilityRatingForProject; @@ -312,6 +313,11 @@ private ThriftEnumUtils() { ClearingRequestPriority.CRITICAL, "Critical" ); + private static final ImmutableMap MAP_USER_ACCESS_STRING = ImmutableMap.builder() + .put(UserAccess.READ, "Read") + .put(UserAccess.READ_WRITE, "Read and Write") + .build(); + public static final ImmutableMap, Map> MAP_ENUMTYPE_MAP = ImmutableMap., Map>builder() .put(ComponentType.class, MAP_COMPONENT_TYPE_STRING) @@ -342,6 +348,7 @@ private ThriftEnumUtils() { .put(ObligationLevel.class, MAP_OBLIGATION_LEVEL_STRING) .put(ObligationType.class, MAP_OBLIGATION_TYPE_STRING) .put(ClearingRequestPriority.class, MAP_CLEARING_REQUEST_PRIORITY_STRING) + .put(UserAccess.class, MAP_USER_ACCESS_STRING) .build(); public static String enumToString(TEnum value) { diff --git a/libraries/lib-datahandler/src/main/thrift/users.thrift b/libraries/lib-datahandler/src/main/thrift/users.thrift index 4d08f81e56..35ec425054 100644 --- a/libraries/lib-datahandler/src/main/thrift/users.thrift +++ b/libraries/lib-datahandler/src/main/thrift/users.thrift @@ -25,6 +25,11 @@ enum UserGroup { CLEARING_EXPERT = 6 } +enum UserAccess { + READ = 0, + READ_WRITE =1 +} + enum LocalGroup { BU = 0, CONTRIBUTOR = 1, @@ -62,6 +67,12 @@ struct User { 22: optional map> secondaryDepartmentsAndRoles, 23: optional list primaryRoles, 24: optional bool deactivated + 25: optional map oidcClientInfos, +} + +struct ClientMetadata { + 1: required string name, + 2: required UserAccess access } struct RestApiToken { @@ -89,6 +100,11 @@ service UserService { **/ User getByApiToken(1:string token); + /** + * returns SW360-user with given client id + **/ + User getByOidcClientId(1:string clientId); + /** * searches for a SW360 user by email, or, if no such user is found, by externalId **/ diff --git a/rest/resource-server/pom.xml b/rest/resource-server/pom.xml index 4b62ca0720..6009d458ce 100644 --- a/rest/resource-server/pom.xml +++ b/rest/resource-server/pom.xml @@ -264,6 +264,11 @@ spring-security-oauth2-autoconfigure 2.6.6 + + org.bitbucket.b_c + jose4j + 0.7.9 + diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java index 12f301140d..561a2285b5 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/Sw360ResourceServer.java @@ -51,6 +51,9 @@ public class Sw360ResourceServer extends SpringBootServletInitializer { public static final String API_TOKEN_MAX_VALIDITY_WRITE_IN_DAYS; public static final Set DOMAIN; public static final String REPORT_FILENAME_MAPPING; + public static final String JWKS_ISSUER_URL; + public static final String JWKS_ENDPOINT_URL; + public static final Boolean IS_JWKS_VALIDATION_ENABLED; static { Properties props = CommonUtils.loadProperties(Sw360ResourceServer.class, SW360_PROPERTIES_FILE_PATH); @@ -60,6 +63,9 @@ public class Sw360ResourceServer extends SpringBootServletInitializer { DOMAIN = CommonUtils.splitToSet(props.getProperty("domain", "Application Software, Documentation, Embedded Software, Hardware, Test and Diagnostics")); REPORT_FILENAME_MAPPING = props.getProperty("org.eclipse.sw360.licensinfo.projectclearing.templatemapping", ""); + JWKS_ISSUER_URL = props.getProperty("jwks.issuer.url", null); + JWKS_ENDPOINT_URL = props.getProperty("jwks.endpoint.url", null); + IS_JWKS_VALIDATION_ENABLED = Boolean.parseBoolean(props.getProperty("jwks.validation.enabled", "false")); } @Bean diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index 46d523741a..f39be64af9 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -278,7 +278,9 @@ static abstract class EmbeddedProjectMixin extends ProjectMixin { "primaryRoles", "primaryRolesSize", "setPrimaryRoles", - "setDeactivated" + "setDeactivated", + "oidcClientInfosSize", + "setOidcClientInfos" }) static abstract class UserMixin extends User { @Override diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/SimpleAuthenticationEntryPoint.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/SimpleAuthenticationEntryPoint.java new file mode 100644 index 0000000000..eb02d5fd09 --- /dev/null +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/SimpleAuthenticationEntryPoint.java @@ -0,0 +1,47 @@ +/* + * Copyright Siemens AG, 2021. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.sw360.rest.resourceserver.core; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Calendar; +import java.util.HashMap; +import java.util.LinkedHashMap; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class SimpleAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + HashMap map = new LinkedHashMap<>(); + map.put("status", HttpServletResponse.SC_UNAUTHORIZED); + map.put("message", authException.getMessage()); + map.put("timestamp", Calendar.getInstance().getTime().toGMTString()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setCharacterEncoding("utf-8"); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + ObjectMapper objectMapper = new ObjectMapper(); + String resBody = objectMapper.writeValueAsString(map); + PrintWriter printWriter = response.getWriter(); + printWriter.print(resBody); + printWriter.flush(); + printWriter.close(); + } +} \ No newline at end of file diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java index ee511e70e9..bbdd7705dd 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/ResourceServerConfiguration.java @@ -10,6 +10,7 @@ package org.eclipse.sw360.rest.resourceserver.security; +import org.eclipse.sw360.rest.resourceserver.core.SimpleAuthenticationEntryPoint; import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationFilter; import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationProvider; import org.springframework.beans.factory.annotation.Autowired; @@ -26,6 +27,7 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @Profile("!SECURITY_MOCK") @@ -67,7 +69,7 @@ public void configure(WebSecurity web) { public void configure(HttpSecurity http) throws Exception { // TODO Thomas Maier 15-12-2017 // Use Sw360GrantedAuthority from authorization server - + SimpleAuthenticationEntryPoint saep = new SimpleAuthenticationEntryPoint(); http .addFilterBefore(filter, BasicAuthenticationFilter.class) .authenticationProvider(authProvider) @@ -82,6 +84,6 @@ public void configure(HttpSecurity http) throws Exception { .antMatchers(HttpMethod.PUT, "/api/**").hasAuthority("WRITE") .antMatchers(HttpMethod.DELETE, "/api/**").hasAuthority("WRITE") .antMatchers(HttpMethod.PATCH, "/api/**").hasAuthority("WRITE").and() - .csrf().disable(); + .csrf().disable().exceptionHandling().authenticationEntryPoint(saep); } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/apiToken/ApiTokenAuthenticationFilter.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/apiToken/ApiTokenAuthenticationFilter.java index 5b9418abb1..a06387b1ad 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/apiToken/ApiTokenAuthenticationFilter.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/apiToken/ApiTokenAuthenticationFilter.java @@ -12,6 +12,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.eclipse.sw360.rest.resourceserver.Sw360ResourceServer; import org.springframework.context.annotation.Profile; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -32,6 +33,7 @@ public class ApiTokenAuthenticationFilter implements Filter { private static final Logger log = LogManager.getLogger(ApiTokenAuthenticationFilter.class); private static final String AUTHENTICATION_TOKEN_PARAMETER = "authorization"; + private static final String OIDC_AUTHENTICATION_TOKEN_PARAMETER = "oidcauthorization"; @Override public void init(FilterConfig filterConfig) { @@ -53,6 +55,14 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha Authentication auth = new ApiTokenAuthentication(token[1]); SecurityContextHolder.getContext().setAuthentication(auth); } + } else if (Sw360ResourceServer.IS_JWKS_VALIDATION_ENABLED && !headers.isEmpty() + && headers.containsKey(OIDC_AUTHENTICATION_TOKEN_PARAMETER)) { + String authorization = headers.get(OIDC_AUTHENTICATION_TOKEN_PARAMETER); + String[] token = authorization.trim().split("\\s+"); + if (token.length == 2 && token[0].equalsIgnoreCase("Bearer")) { + Authentication auth = new ApiTokenAuthentication(token[1]).setType(AuthType.JWKS); + SecurityContextHolder.getContext().setAuthentication(auth); + } } } @@ -63,10 +73,17 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha public void destroy() { } + enum AuthType { + JWKS; + } + class ApiTokenAuthentication implements Authentication { private static final long serialVersionUID = 1L; + private String token; + private AuthType type; + private ApiTokenAuthentication(String token) { this.token = token; } @@ -104,5 +121,14 @@ public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentExce public String getName() { return null; } + + public AuthType getType() { + return type; + } + + public ApiTokenAuthentication setType(AuthType type) { + this.type = type; + return this; + } } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/apiToken/ApiTokenAuthenticationProvider.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/apiToken/ApiTokenAuthenticationProvider.java index 576f05c6de..640f8302e8 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/apiToken/ApiTokenAuthenticationProvider.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/apiToken/ApiTokenAuthenticationProvider.java @@ -14,15 +14,24 @@ import org.apache.commons.lang.time.DateUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.common.SW360Utils; import org.eclipse.sw360.datahandler.thrift.users.RestApiToken; import org.eclipse.sw360.datahandler.thrift.users.User; +import org.eclipse.sw360.datahandler.thrift.users.UserAccess; +import org.eclipse.sw360.rest.resourceserver.Sw360ResourceServer; +import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationFilter.ApiTokenAuthentication; +import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationFilter.AuthType; +import org.eclipse.sw360.rest.resourceserver.security.jwksvalidation.JWTValidator; import org.eclipse.sw360.rest.resourceserver.user.Sw360UserService; import org.jetbrains.annotations.NotNull; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.core.Authentication; @@ -37,6 +46,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.lang.Math.min; import static org.eclipse.sw360.rest.resourceserver.Sw360ResourceServer.*; @@ -50,7 +60,7 @@ public class ApiTokenAuthenticationProvider implements AuthenticationProvider { @NotNull private final Sw360UserService userService; - + @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication.isAuthenticated()) { @@ -60,24 +70,44 @@ public Authentication authenticate(Authentication authentication) throws Authent // Get the corresponding sw360 user and restApiToken based on entered token String tokenFromAuthentication = (String) authentication.getCredentials(); - String tokenHash = BCrypt.hashpw(tokenFromAuthentication, API_TOKEN_HASH_SALT); - User sw360User = getUserFromTokenHash(tokenHash); - if (sw360User == null || sw360User.isDeactivated()) { - throw new DisabledException("User is deactivated"); - } - Optional restApiToken = getApiTokenFromUser(tokenHash, sw360User); + if (Sw360ResourceServer.IS_JWKS_VALIDATION_ENABLED && authentication instanceof ApiTokenAuthentication + && ((ApiTokenAuthentication) authentication).getType() == AuthType.JWKS) { + JWTValidator validator = new JWTValidator(Sw360ResourceServer.JWKS_ISSUER_URL, + Sw360ResourceServer.JWKS_ENDPOINT_URL); + JwtClaims jwtClaims = null; + try { + jwtClaims = validator.validateJWT(tokenFromAuthentication); + } catch (InvalidJwtException exp) { + throw new BadCredentialsException(exp.getMessage()); + } + Object clientIdAsObject = jwtClaims.getClaimValue("client_id"); + if (clientIdAsObject == null || clientIdAsObject.toString().isBlank()) { + throw new BadCredentialsException("Client Id cannot be null or empty"); + } - if (restApiToken.isPresent()) { - if (!isApiTokenExpired(restApiToken.get())) { - // User authenticated successfully - log.trace("Valid token authentication for user: " + sw360User.getEmail()); - return authenticatedApiUser(sw360User, tokenFromAuthentication, restApiToken.get()); + String clientIdAsStr = clientIdAsObject.toString(); + User sw360User = getUserFromClientId(clientIdAsStr); + return authenticatedOidcUser(sw360User, clientIdAsStr); + } else { + String tokenHash = BCrypt.hashpw(tokenFromAuthentication, API_TOKEN_HASH_SALT); + User sw360User = getUserFromTokenHash(tokenHash); + if (sw360User == null || sw360User.isDeactivated()) { + throw new DisabledException("User is deactivated"); + } + Optional restApiToken = getApiTokenFromUser(tokenHash, sw360User); + + if (restApiToken.isPresent()) { + if (!isApiTokenExpired(restApiToken.get())) { + // User authenticated successfully + log.trace("Valid token authentication for user: " + sw360User.getEmail()); + return authenticatedApiUser(sw360User, tokenFromAuthentication, restApiToken.get()); + } else { + throw new CredentialsExpiredException("Your entered API token is expired."); + } } else { - throw new CredentialsExpiredException("Your entered API token is expired."); + log.trace("Could not load API token form user " + sw360User.getEmail()); + throw new AuthenticationServiceException("Your entered API token is not valid."); } - } else { - log.trace("Could not load API token form user " + sw360User.getEmail()); - throw new AuthenticationServiceException("Your entered API token is not valid."); } } @@ -90,6 +120,16 @@ private User getUserFromTokenHash(String tokenHash) { } } + private User getUserFromClientId(String clientId) { + try { + return userService.getUserFromClientId(clientId); + } catch (RuntimeException e) { + log.debug("Could not find any user for the entered clientId " + clientId); + throw new AuthenticationServiceException( + "Your entered OIDC token is not associated with any user for authorization."); + } + } + private Optional getApiTokenFromUser(String tokenHash, User sw360User) { return sw360User.getRestApiTokens() .stream() @@ -112,6 +152,12 @@ private Set getGrantedAuthoritiesFromApiToken(RestApiToken res .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); } + + private Set getGrantedAuthoritiesFromUserAccess(UserAccess userAccess) { + return Stream.of(userAccess.name().split("_")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + } private PreAuthenticatedAuthenticationToken authenticatedApiUser(User user, String credentials, RestApiToken restApiToken) { Set grantedAuthorities = getGrantedAuthoritiesFromApiToken(restApiToken); @@ -121,6 +167,15 @@ private PreAuthenticatedAuthenticationToken authenticatedApiUser(User user, Stri return preAuthenticatedAuthenticationToken; } + private PreAuthenticatedAuthenticationToken authenticatedOidcUser(User user, String credentials) { + Set grantedAuthorities = getGrantedAuthoritiesFromUserAccess( + user.getOidcClientInfos().get(credentials).getAccess()); + PreAuthenticatedAuthenticationToken preAuthenticatedAuthenticationToken = new PreAuthenticatedAuthenticationToken( + user.getEmail(), credentials, grantedAuthorities); + preAuthenticatedAuthenticationToken.setAuthenticated(true); + return preAuthenticatedAuthenticationToken; + } + @Override public boolean supports(Class authentication) { return authentication.equals(ApiTokenAuthenticationFilter.ApiTokenAuthentication.class); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/jwksvalidation/JWTValidator.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/jwksvalidation/JWTValidator.java new file mode 100644 index 0000000000..4d3c55a292 --- /dev/null +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/security/jwksvalidation/JWTValidator.java @@ -0,0 +1,49 @@ +/* + * Copyright Siemens AG, 2021. Part of the SW360 Portal Project. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.sw360.rest.resourceserver.security.jwksvalidation; + +import org.jose4j.jwk.HttpsJwks; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumer; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; +import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver; + +public class JWTValidator { + JwtConsumer jwtConsumer; + + /** + * Creates a validator for JWT access tokens issued by the given PF instance. + * + * @param pfBaseUrl the base URL of the PF instance including the trailing + * slash. + */ + public JWTValidator(String issuerUrl, String jwksurl) { + HttpsJwks httpsJkws = new HttpsJwks(jwksurl); + HttpsJwksVerificationKeyResolver httpsJwksKeyResolver = new HttpsJwksVerificationKeyResolver(httpsJkws); + jwtConsumer = new JwtConsumerBuilder() + .setRequireExpirationTime() + .setAllowedClockSkewInSeconds(30) + .setExpectedIssuer(issuerUrl) + .setVerificationKeyResolver(httpsJwksKeyResolver).build(); + } + + /** + * Validates the given JWT access token. + * + * @param jwt the JWT access token. + * @return the claims associated with the given token. + * @throws InvalidJwtException if the JWT could not be validated. + */ + public JwtClaims validateJWT(String jwt) throws InvalidJwtException { + return jwtConsumer.processToClaims(jwt); + } +} \ No newline at end of file diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java index 7f3d6cfbb3..dbc5cdd876 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java @@ -72,6 +72,15 @@ public User getUserByApiToken(String token) { } } + public User getUserFromClientId(String clientId) { + try { + UserService.Iface sw360UserClient = getThriftUserClient(); + return sw360UserClient.getByOidcClientId(clientId); + } catch (TException e) { + throw new RuntimeException(e); + } + } + private UserService.Iface getThriftUserClient() throws TTransportException { THttpClient thriftClient = new THttpClient(thriftServerUrl + "/users/thrift"); TProtocol protocol = new TCompactProtocol(thriftClient); From c1b42abfc2103158b1c5f18d7b0c183d3624b8b5 Mon Sep 17 00:00:00 2001 From: Abdul Kapti Date: Thu, 9 Jun 2022 12:43:56 +0530 Subject: [PATCH 2/7] feat(Vendor):Trim fields, Restrict vendor duplication & create Vendor via REST Signed-off-by: Abdul Kapti --- .../datahandler/db/VendorRepository.java | 11 ++++ .../sw360/vendors/VendorDatabaseHandler.java | 56 +++++++++++++++++-- .../eclipse/sw360/vendors/VendorHandler.java | 8 +-- .../sw360/vendors/VendorHandlerTest.java | 7 ++- .../sw360/portal/common/ErrorMessages.java | 5 ++ .../sw360/portal/portlets/Sw360Portlet.java | 2 +- .../admin/BulkReleaseEditPortlet.java | 15 ++++- .../portal/portlets/admin/VendorPortlet.java | 26 +++++++-- .../portlets/components/ComponentPortlet.java | 12 +++- .../portlets/projects/ProjectPortlet.java | 12 +++- .../resources/html/admin/vendors/edit.jsp | 2 +- .../components/includes/vendors/addVendor.js | 9 ++- .../sw360/importer/ComponentImportUtils.java | 5 +- .../datahandler/thrift/ThriftValidate.java | 5 +- .../src/main/thrift/vendors.thrift | 5 +- .../src/docs/asciidoc/vendors.adoc | 18 ++++++ .../vendor/Sw360VendorService.java | 29 ++++++++-- .../vendor/VendorController.java | 6 +- .../restdocs/VendorSpecTest.java | 40 +++++++++++++ 19 files changed, 230 insertions(+), 43 deletions(-) diff --git a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/VendorRepository.java b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/VendorRepository.java index 4bd9c27943..b57cb5dab6 100644 --- a/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/VendorRepository.java +++ b/backend/src-common/src/main/java/org/eclipse/sw360/datahandler/db/VendorRepository.java @@ -11,8 +11,11 @@ import static com.google.common.base.Strings.isNullOrEmpty; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.eclipse.sw360.datahandler.cloudantclient.DatabaseConnectorCloudant; import org.eclipse.sw360.datahandler.cloudantclient.DatabaseRepositoryCloudantClient; @@ -31,13 +34,21 @@ public class VendorRepository extends DatabaseRepositoryCloudantClient { private static final String ALL = "function(doc) { if (doc.type == 'vendor') emit(null, doc._id) }"; + private static final String BY_FULL_NAME = "function(doc) { if (doc.type == 'vendor' && doc.fullname != null) emit(doc.fullname.toLowerCase(), doc._id) }"; + public VendorRepository(DatabaseConnectorCloudant db) { super(db, Vendor.class); Map views = new HashMap(); views.put("all", createMapReduce(ALL, null)); + views.put("vendorbyfullname", createMapReduce(BY_FULL_NAME, null)); initStandardDesignDocument(views, db); } + public List searchByFullname(String fullname) { + List vendorsMatchingFullname = new ArrayList(get(queryForIdsAsValue("vendorbyfullname", fullname))); + return vendorsMatchingFullname; + } + public void fillVendor(Component component) { if (component.isSetDefaultVendorId()) { final String vendorId = component.getDefaultVendorId(); diff --git a/backend/src/src-vendors/src/main/java/org/eclipse/sw360/vendors/VendorDatabaseHandler.java b/backend/src/src-vendors/src/main/java/org/eclipse/sw360/vendors/VendorDatabaseHandler.java index 1148977dc7..5f98dbf5de 100644 --- a/backend/src/src-vendors/src/main/java/org/eclipse/sw360/vendors/VendorDatabaseHandler.java +++ b/backend/src/src-vendors/src/main/java/org/eclipse/sw360/vendors/VendorDatabaseHandler.java @@ -16,6 +16,8 @@ import org.eclipse.sw360.datahandler.cloudantclient.DatabaseConnectorCloudant; import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.db.VendorRepository; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.RequestSummary; import org.eclipse.sw360.datahandler.thrift.SW360Exception; @@ -64,10 +66,25 @@ public List getAllVendors() throws TException { return repository.getAll(); } - public String addVendor(Vendor vendor) throws TException { - prepareVendor(vendor); + public AddDocumentRequestSummary addVendor(Vendor vendor) { + trimVendorFields(vendor); + if (isDuplicate(vendor)) { + return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.DUPLICATE); + } + try { + prepareVendor(vendor); + } catch (SW360Exception e) { + log.error("Error creating the vendor: " + e.why); + return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.FAILURE).setMessage(e.why); + } repository.add(vendor); - return vendor.getId(); + return new AddDocumentRequestSummary().setRequestStatus(AddDocumentRequestStatus.SUCCESS).setId(vendor.getId()); + } + + // always perform for case insensitive check + private boolean isDuplicate(Vendor vendor) { + List duplicates = repository.searchByFullname(vendor.getFullname().toLowerCase()); + return duplicates.size() > 0; } public RequestStatus deleteVendor(String id, User user) throws SW360Exception { @@ -83,16 +100,43 @@ public RequestStatus deleteVendor(String id, User user) throws SW360Exception { } } - public RequestStatus updateVendor(Vendor vendor, User user) { - if (makePermission(vendor, user).isActionAllowed(RequestedAction.WRITE)) { + public RequestStatus updateVendor(Vendor vendor, User user) throws SW360Exception { + Vendor actual = repository.get(vendor.getId()); + assertNotNull(actual); + trimVendorFields(vendor); + trimVendorFields(actual); + if (isChangeResultInDuplicate(actual, vendor)) { + return RequestStatus.DUPLICATE; + } else if (makePermission(vendor, user).isActionAllowed(RequestedAction.WRITE)) { + try { + prepareVendor(vendor); + } catch (SW360Exception e) { + log.error("Error updating the vendor: " + e.why); + return RequestStatus.FAILURE; + } repository.update(vendor); return RequestStatus.SUCCESS; } else { - log.error("User is not allowed to delete!"); + log.error("User is not allowed to update!"); return RequestStatus.FAILURE; } } + private void trimVendorFields(Vendor vendor) { + vendor.setUrl(CommonUtils.nullToEmptyString(vendor.getUrl()).trim()); + vendor.setFullname(CommonUtils.nullToEmptyString(vendor.getFullname()).trim()); + vendor.setShortname(CommonUtils.nullToEmptyString(vendor.getShortname()).trim()); + } + + private boolean isChangeResultInDuplicate(Vendor before, Vendor after) { + if (CommonUtils.nullToEmptyString(before.getFullname()).equals(after.getFullname()) && + CommonUtils.nullToEmptyString(before.getUrl()).equals(after.getUrl())) { + // not duplicated, because fullname and url is same + return false; + } + return isDuplicate(after); + } + public void fillVendor(Release release){ repository.fillVendor(release); } diff --git a/backend/src/src-vendors/src/main/java/org/eclipse/sw360/vendors/VendorHandler.java b/backend/src/src-vendors/src/main/java/org/eclipse/sw360/vendors/VendorHandler.java index 5718ee607a..16260915c9 100644 --- a/backend/src/src-vendors/src/main/java/org/eclipse/sw360/vendors/VendorHandler.java +++ b/backend/src/src-vendors/src/main/java/org/eclipse/sw360/vendors/VendorHandler.java @@ -14,6 +14,7 @@ import org.eclipse.sw360.datahandler.common.DatabaseSettings; import org.eclipse.sw360.datahandler.couchdb.DatabaseConnector; import org.eclipse.sw360.datahandler.db.VendorSearchHandler; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; @@ -87,13 +88,11 @@ public List searchVendorIds(String searchText) throws TException { @Override - public String addVendor(Vendor vendor) throws TException { + public AddDocumentRequestSummary addVendor(Vendor vendor) throws TException { assertNotNull(vendor); assertIdUnset(vendor.getId()); - vendorDatabaseHandler.addVendor(vendor); - - return vendor.getId(); + return vendorDatabaseHandler.addVendor(vendor); } @Override @@ -108,6 +107,7 @@ public RequestStatus deleteVendor(String id, User user) throws TException { public RequestStatus updateVendor(Vendor vendor, User user) throws TException { assertUser(user); assertNotNull(vendor); + assertId(vendor.getId()); return vendorDatabaseHandler.updateVendor(vendor, user); } diff --git a/backend/src/src-vendors/src/test/java/org/eclipse/sw360/vendors/VendorHandlerTest.java b/backend/src/src-vendors/src/test/java/org/eclipse/sw360/vendors/VendorHandlerTest.java index db7d5bf878..5b86901af7 100644 --- a/backend/src/src-vendors/src/test/java/org/eclipse/sw360/vendors/VendorHandlerTest.java +++ b/backend/src/src-vendors/src/test/java/org/eclipse/sw360/vendors/VendorHandlerTest.java @@ -13,6 +13,7 @@ import org.eclipse.sw360.datahandler.common.DatabaseSettingsTest; import org.eclipse.sw360.datahandler.couchdb.DatabaseConnector; import org.eclipse.sw360.datahandler.couchdb.DatabaseInstance; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.junit.After; import org.junit.Before; @@ -78,11 +79,11 @@ public void testGetAllVendors() throws Exception { @Test public void testAddVendor() throws Exception { Vendor oracle = new Vendor().setShortname("Oracle").setFullname("Oracle Corporation Inc").setUrl("http://www.oracle.com"); - String id = vendorHandler.addVendor(oracle); - assertNotNull(id); + AddDocumentRequestSummary summary = vendorHandler.addVendor(oracle); + assertNotNull(summary.getId()); assertEquals(vendorList.size() + 1, vendorHandler.getAllVendors().size()); - Vendor actual = vendorHandler.getByID(id); + Vendor actual = vendorHandler.getByID(summary.getId()); assertVendorEquals(oracle, actual); } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java index bd5505bcb0..e93909f50c 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/ErrorMessages.java @@ -63,6 +63,9 @@ public class ErrorMessages { public static final String LICENSE_TYPE_ACCESS_DENIED = "User does not have the permission to add license type."; public static final String OBLIGATION_NOT_ADDED = "Obligation could not be added."; public static final String OBLIGATION_NOT_UPDATED = "Obligation could not be updated."; + public static final String VENDOR_DUPLICATE = "A vendor with the same name already exists."; + public static final String ERROR_VENDOR = "Error: Invalid vendor Name or Url."; + //this map is used in errorKeyToMessage.jspf to generate key-value pairs for the liferay-ui error tag public static final ImmutableList allErrorMessages = ImmutableList.builder() @@ -113,6 +116,8 @@ public class ErrorMessages { .add(ERROR_USER_ACTIVATE_DEACTIVATE) .add(OBLIGATION_NOT_ADDED) .add(OBLIGATION_NOT_UPDATED) + .add(VENDOR_DUPLICATE) + .add(ERROR_VENDOR) .build(); private ErrorMessages() { diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/Sw360Portlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/Sw360Portlet.java index 4e6e4b3684..dcbe519363 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/Sw360Portlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/Sw360Portlet.java @@ -291,7 +291,7 @@ public void setSessionMessage(PortletRequest request, RequestStatus requestStatu } switch (requestStatus) { case SUCCESS: - statusMessage = type + name + " " + verb + "d successfully!"; + statusMessage = new StringBuilder(type).append(" ").append(name).append(" ").append(verb).append("d successfully!").toString(); SessionMessages.add(request, "request_processed", statusMessage); break; case SENT_TO_MODERATOR: diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/BulkReleaseEditPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/BulkReleaseEditPortlet.java index 3308db9566..0de4d28fca 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/BulkReleaseEditPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/BulkReleaseEditPortlet.java @@ -13,12 +13,15 @@ import com.liferay.portal.kernel.json.JSONObject; import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.components.ComponentService; import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.eclipse.sw360.datahandler.thrift.vendors.VendorService; +import org.eclipse.sw360.portal.common.ErrorMessages; import org.eclipse.sw360.portal.common.PortalConstants; import org.eclipse.sw360.portal.portlets.Sw360Portlet; import org.eclipse.sw360.portal.portlets.components.ComponentPortletUtils; @@ -147,9 +150,17 @@ private void serveAddVendor(ResourceRequest request, ResourceResponse response) try { VendorService.Iface client = thriftClients.makeVendorClient(); - String vendorId = client.addVendor(vendor); + AddDocumentRequestSummary summary = client.addVendor(vendor); + AddDocumentRequestStatus status = summary.getRequestStatus(); JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); - jsonObject.put("id", vendorId); + + if (AddDocumentRequestStatus.SUCCESS.equals(status)) { + jsonObject.put("id", summary.getId()); + } else if (AddDocumentRequestStatus.DUPLICATE.equals(status)) { + jsonObject.put("error", ErrorMessages.VENDOR_DUPLICATE); + } else if (AddDocumentRequestStatus.FAILURE.equals(status)) { + jsonObject.put("error", summary.getMessage()); + } try { writeJSON(request, response, jsonObject); } catch (IOException e) { diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/VendorPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/VendorPortlet.java index 95d13a69a0..19b70917b4 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/VendorPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/admin/VendorPortlet.java @@ -22,6 +22,8 @@ import com.liferay.portal.kernel.util.WebKeys; import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.SW360Exception; import org.eclipse.sw360.datahandler.thrift.components.Component; @@ -31,6 +33,7 @@ import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.eclipse.sw360.datahandler.thrift.vendors.VendorService; import org.eclipse.sw360.exporter.VendorExporter; +import org.eclipse.sw360.portal.common.ErrorMessages; import org.eclipse.sw360.portal.common.PortalConstants; import org.eclipse.sw360.portal.common.UsedAsLiferayAction; import org.eclipse.sw360.portal.portlets.Sw360Portlet; @@ -206,7 +209,13 @@ public void updateVendor(ActionRequest request, ActionResponse response) throws Vendor vendor = vendorClient.getByID(id); ComponentPortletUtils.updateVendorFromRequest(request, vendor); RequestStatus requestStatus = vendorClient.updateVendor(vendor, user); - setSessionMessage(request, requestStatus, "Vendor", "update", vendor.getShortname()); + if (RequestStatus.SUCCESS.equals(requestStatus)) { + setSessionMessage(request, requestStatus, "Vendor", "update", vendor.getFullname()); + } else if (RequestStatus.DUPLICATE.equals(requestStatus)) { + setSW360SessionError(request, ErrorMessages.VENDOR_DUPLICATE); + } else if (RequestStatus.FAILURE.equals(requestStatus)) { + setSW360SessionError(request, ErrorMessages.ERROR_VENDOR); + } } catch (TException e) { log.error("Error fetching vendor from backend!", e); } @@ -223,13 +232,22 @@ public void removeVendor(ActionRequest request, ActionResponse response) throws response.setRenderParameter(PAGENAME, PAGENAME_VIEW); } - private void addVendor(ActionRequest request) { + private void addVendor(ActionRequest request) throws PortletException { final Vendor vendor = new Vendor(); ComponentPortletUtils.updateVendorFromRequest(request, vendor); try { - VendorService.Iface vendorClient = thriftClients.makeVendorClient(); - String vendorId = vendorClient.addVendor(vendor); + VendorService.Iface client = thriftClients.makeVendorClient(); + AddDocumentRequestSummary summary = client.addVendor(vendor); + AddDocumentRequestStatus status = summary.getRequestStatus(); + + if (AddDocumentRequestStatus.SUCCESS.equals(status)) { + setSessionMessage(request, RequestStatus.SUCCESS, "Vendor", "adde", vendor.getFullname()); + } else if (AddDocumentRequestStatus.DUPLICATE.equals(status)) { + setSW360SessionError(request, ErrorMessages.VENDOR_DUPLICATE); + } else if (AddDocumentRequestStatus.FAILURE.equals(status)) { + setSW360SessionError(request, ErrorMessages.ERROR_VENDOR); + } } catch (TException e) { log.error("Error adding vendor", e); } diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java index b5b6e27cae..43e9f0d177 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java @@ -332,9 +332,17 @@ private void serveAddVendor(ResourceRequest request, ResourceResponse response) try { VendorService.Iface client = thriftClients.makeVendorClient(); - String vendorId = client.addVendor(vendor); + AddDocumentRequestSummary summary = client.addVendor(vendor); + AddDocumentRequestStatus status = summary.getRequestStatus(); JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); - jsonObject.put("id", vendorId); + + if (AddDocumentRequestStatus.SUCCESS.equals(status)) { + jsonObject.put("id", summary.getId()); + } else if (AddDocumentRequestStatus.DUPLICATE.equals(status)) { + jsonObject.put("error", ErrorMessages.VENDOR_DUPLICATE); + } else if (AddDocumentRequestStatus.FAILURE.equals(status)) { + jsonObject.put("error", summary.getMessage()); + } try { writeJSON(request, response, jsonObject); } catch (IOException e) { diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java index b267e4e717..a99f2dbca4 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/projects/ProjectPortlet.java @@ -328,9 +328,17 @@ private void serveAddVendor(ResourceRequest request, ResourceResponse response) try { VendorService.Iface client = thriftClients.makeVendorClient(); - String vendorId = client.addVendor(vendor); + AddDocumentRequestSummary summary = client.addVendor(vendor); + AddDocumentRequestStatus status = summary.getRequestStatus(); JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); - jsonObject.put("id", vendorId); + + if (AddDocumentRequestStatus.SUCCESS.equals(status)) { + jsonObject.put("id", summary.getId()); + } else if (AddDocumentRequestStatus.DUPLICATE.equals(status)) { + jsonObject.put("error", ErrorMessages.VENDOR_DUPLICATE); + } else if (AddDocumentRequestStatus.FAILURE.equals(status)) { + jsonObject.put("error", summary.getMessage()); + } try { writeJSON(request, response, jsonObject); } catch (IOException e) { diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/vendors/edit.jsp b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/vendors/edit.jsp index 4e7baddcc0..0c97d820f0 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/vendors/edit.jsp +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/admin/vendors/edit.jsp @@ -99,7 +99,7 @@
    - " name="<%=Vendor._Fields.URL%>" + " name="<%=Vendor._Fields.URL%>" value="" />
    diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/js/components/includes/vendors/addVendor.js b/frontend/sw360-portlet/src/main/resources/META-INF/resources/js/components/includes/vendors/addVendor.js index ab5ef0d082..9319a50db5 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/js/components/includes/vendors/addVendor.js +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/js/components/includes/vendors/addVendor.js @@ -40,8 +40,13 @@ define('components/includes/vendors/addVendor', ['jquery', 'modules/dialog', 'mo url: addVendorUrl, data: data, success: function (data) { - vendorAddedCb(data.id + ',' + fullname); - callback(true); + if (data.id) { + vendorAddedCb(data.id + ',' + fullname); + callback(true); + } else { + $dialog.alert(data.error ? data.error : "Failed to add vendor!"); + callback(); + } }, error: function() { $dialog.alert('Cannot add vendor.'); diff --git a/libraries/importers/src/main/java/org/eclipse/sw360/importer/ComponentImportUtils.java b/libraries/importers/src/main/java/org/eclipse/sw360/importer/ComponentImportUtils.java index 5197c7db94..18fb42d4ae 100644 --- a/libraries/importers/src/main/java/org/eclipse/sw360/importer/ComponentImportUtils.java +++ b/libraries/importers/src/main/java/org/eclipse/sw360/importer/ComponentImportUtils.java @@ -26,6 +26,7 @@ import org.eclipse.sw360.datahandler.thrift.components.Component; import org.eclipse.sw360.datahandler.thrift.components.ComponentService; import org.eclipse.sw360.datahandler.thrift.components.Release; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.ReleaseRelationship; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; @@ -453,8 +454,8 @@ public static Map getVendorNameToId(Iterable String vendorName = componentCSVRecord.getVendorName(); if (!vendorNameToVendorId.containsKey(vendorName)) { Vendor vendor = componentCSVRecord.getVendor(); - String vendorId = vendorClient.addVendor(vendor); - + AddDocumentRequestSummary summary = vendorClient.addVendor(vendor); + String vendorId = summary.getId(); vendorNameToVendorId.put(vendorName, vendorId); log.trace(format("created vendor with name '%s' as %s: %s", vendorName, vendorId, vendor)); } else { diff --git a/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/thrift/ThriftValidate.java b/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/thrift/ThriftValidate.java index 570a8868a4..f3df764e9e 100644 --- a/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/thrift/ThriftValidate.java +++ b/libraries/lib-datahandler/src/main/java/org/eclipse/sw360/datahandler/thrift/ThriftValidate.java @@ -120,8 +120,9 @@ public static void prepareUser(User user) throws SW360Exception { public static void prepareVendor(Vendor vendor) throws SW360Exception { // Check required fields - assertNotEmpty(vendor.getShortname()); - assertNotEmpty(vendor.getFullname()); + assertNotEmpty(vendor.getShortname(), "vendor short name cannot be empty!"); + assertNotEmpty(vendor.getFullname(), "vendor full name cannot be empty!"); + assertValidUrl(vendor.getUrl()); // Check type vendor.setType(TYPE_VENDOR); diff --git a/libraries/lib-datahandler/src/main/thrift/vendors.thrift b/libraries/lib-datahandler/src/main/thrift/vendors.thrift index f00120bb97..942a583fa2 100644 --- a/libraries/lib-datahandler/src/main/thrift/vendors.thrift +++ b/libraries/lib-datahandler/src/main/thrift/vendors.thrift @@ -17,6 +17,7 @@ namespace php sw360.thrift.vendors typedef sw360.RequestStatus RequestStatus typedef users.User User typedef users.RequestedAction RequestedAction +typedef sw360.AddDocumentRequestSummary AddDocumentRequestSummary struct Vendor { 1: optional string id, @@ -57,9 +58,9 @@ service VendorService { list searchVendorIds(1: string searchText); /** - * write vendor to database and return id + * write vendor to database and return id with status summary **/ - string addVendor(1: Vendor vendor); + AddDocumentRequestSummary addVendor(1: Vendor vendor); /** * vendor specified by id is deleted from database if user has sufficient permissions, otherwise FAILURE is returned diff --git a/rest/resource-server/src/docs/asciidoc/vendors.adoc b/rest/resource-server/src/docs/asciidoc/vendors.adoc index af7796ac69..cc762b148e 100644 --- a/rest/resource-server/src/docs/asciidoc/vendors.adoc +++ b/rest/resource-server/src/docs/asciidoc/vendors.adoc @@ -48,3 +48,21 @@ include::{snippets}/should_document_get_vendor/http-response.adoc[] ===== Links include::{snippets}/should_document_get_vendor/links.adoc[] + + +[[resources-vendor-create]] +==== Creating a vendor + +A `POST` request is used to create a vendor. + +===== Request structure +include::{snippets}/should_document_create_vendor/request-fields.adoc[] + +===== Example request +include::{snippets}/should_document_create_vendor/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_create_vendor/http-response.adoc[] + +===== Links +include::{snippets}/should_document_create_vendor/links.adoc[] \ No newline at end of file diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/vendor/Sw360VendorService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/vendor/Sw360VendorService.java index f4d29f9bfc..27fc06a631 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/vendor/Sw360VendorService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/vendor/Sw360VendorService.java @@ -11,24 +11,28 @@ package org.eclipse.sw360.rest.resourceserver.vendor; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.thrift.TException; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.THttpClient; import org.apache.thrift.transport.TTransportException; +import org.eclipse.sw360.datahandler.common.CommonUtils; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestStatus; +import org.eclipse.sw360.datahandler.thrift.AddDocumentRequestSummary; import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.eclipse.sw360.datahandler.thrift.vendors.VendorService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.stereotype.Service; + import java.util.List; @Service -@Slf4j @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class Sw360VendorService { @Value("${sw360.thrift-server-url:http://localhost:8080}") @@ -69,9 +73,20 @@ public Vendor getVendorByFullName(String fullName) { public Vendor createVendor(Vendor vendor) { try { VendorService.Iface sw360VendorClient = getThriftVendorClient(); - String vendorId = sw360VendorClient.addVendor(vendor); - vendor.setId(vendorId); - return vendor; + if (CommonUtils.isNullEmptyOrWhitespace(vendor.getFullname()) || CommonUtils.isNullEmptyOrWhitespace(vendor.getShortname()) + || CommonUtils.isNullEmptyOrWhitespace(vendor.getUrl())) { + throw new HttpMessageNotReadableException("A Vendor cannot have null or empty 'Full Name' or 'Short Name' or 'URL'!"); + } + AddDocumentRequestSummary summary = sw360VendorClient.addVendor(vendor); + if (AddDocumentRequestStatus.SUCCESS.equals(summary.getRequestStatus())) { + vendor.setId(summary.getId()); + return vendor; + } else if (AddDocumentRequestStatus.DUPLICATE.equals(summary.getRequestStatus())) { + throw new DataIntegrityViolationException("A Vendor with same full name '" + vendor.getFullname() + "' and URL already exists!"); + } else if (AddDocumentRequestStatus.FAILURE.equals(summary.getRequestStatus())) { + throw new HttpMessageNotReadableException(summary.getMessage()); + } + return null; } catch (TException e) { throw new RuntimeException(e); } @@ -81,8 +96,10 @@ public void updateVendor(Vendor vendor, User sw360User) { try { VendorService.Iface sw360VendorClient = getThriftVendorClient(); RequestStatus requestStatus = sw360VendorClient.updateVendor(vendor, sw360User); - if (requestStatus == RequestStatus.SUCCESS) { + if (RequestStatus.SUCCESS.equals(requestStatus)) { return; + } else if (RequestStatus.DUPLICATE.equals(requestStatus)) { + throw new DataIntegrityViolationException("A Vendor with same full name '" + vendor.getFullname() + "' and URL already exists!"); } throw new RuntimeException("sw360 vendor with full name '" + vendor.getFullname() + " cannot be updated."); } catch (TException e) { diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/vendor/VendorController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/vendor/VendorController.java index 13e8543313..132001e166 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/vendor/VendorController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/vendor/VendorController.java @@ -11,7 +11,6 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.eclipse.sw360.rest.resourceserver.core.HalResource; import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper; @@ -37,7 +36,6 @@ import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @BasePathAwareController -@Slf4j @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class VendorController implements RepresentationModelProcessor { public static final String VENDORS_URL = "/vendors"; @@ -46,7 +44,7 @@ public class VendorController implements RepresentationModelProcessor restControllerHelper; @RequestMapping(value = VENDORS_URL, method = RequestMethod.GET) public ResponseEntity>> getVendors() { @@ -72,7 +70,7 @@ public ResponseEntity> getVendor( @PreAuthorize("hasAuthority('WRITE')") @RequestMapping(value = VENDORS_URL, method = RequestMethod.POST) - public ResponseEntity createVendor( + public ResponseEntity createVendor( @RequestBody Vendor vendor) { vendor = vendorService.createVendor(vendor); HalResource halResource = createHalVendor(vendor); diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/VendorSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/VendorSpecTest.java index 343f4f8c3c..80f72c79df 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/VendorSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/VendorSpecTest.java @@ -21,15 +21,22 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringJUnit4ClassRunner.class) @@ -66,6 +73,9 @@ public void before() { given(this.vendorServiceMock.getVendors()).willReturn(vendorList); given(this.vendorServiceMock.getVendorById(eq(vendor.getId()))).willReturn(vendor); + + when(this.vendorServiceMock.createVendor(any())).then(invocation -> + new Vendor ("Apache", "Apache Software Foundation", "https://www.apache.org/").setId("987567468")); } @Test @@ -104,4 +114,34 @@ public void should_document_get_vendor() throws Exception { subsectionWithPath("_links").description("<> to other resources") ))); } + + + @Test + public void should_document_create_vendor() throws Exception { + Map vendor = new HashMap<>(); + vendor.put("fullName", "Apache Software Foundation"); + vendor.put("shortName", "Apache"); + vendor.put("url", "https://www.apache.org/"); + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + mockMvc.perform(post("/api/vendors/") + .contentType(MediaTypes.HAL_JSON) + .content(this.objectMapper.writeValueAsString(vendor)) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isCreated()) + .andDo(this.documentationHandler.document( + links( + linkWithRel("self").description("The <>") + ), + requestFields( + fieldWithPath("fullName").description("The full name of the vendor"), + fieldWithPath("shortName").description("The short name of the vendor"), + fieldWithPath("url").description("The vendor's home page URL") + ), + responseFields( + subsectionWithPath("fullName").description("The full name of the vendor"), + subsectionWithPath("shortName").description("The short name of the vendor, optional"), + subsectionWithPath("url").description("The vendor's home page URL"), + subsectionWithPath("_links").description("<> to other resources") + ))); + } } From 8efc487102c37077a34f24d265c3ac87fb034c13 Mon Sep 17 00:00:00 2001 From: Nikesh kumar Date: Tue, 21 Jun 2022 15:37:57 +0530 Subject: [PATCH 3/7] fix(rest): Added release main licenses in the response Signed-off-by: Nikesh kumar --- .../resourceserver/core/RestControllerHelper.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java index fb906bce53..5d1989b84a 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java @@ -33,6 +33,7 @@ import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; import org.eclipse.sw360.datahandler.thrift.MainlineState; import org.eclipse.sw360.datahandler.thrift.ProjectReleaseRelationship; +import org.eclipse.sw360.datahandler.thrift.Quadratic; import org.eclipse.sw360.datahandler.thrift.SW360Exception; import org.eclipse.sw360.rest.resourceserver.obligation.ObligationController; import org.eclipse.sw360.rest.resourceserver.project.Sw360ProjectService; @@ -51,6 +52,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.hateoas.*; import org.springframework.hateoas.server.core.EmbeddedWrapper; import org.springframework.hateoas.server.core.EmbeddedWrappers; @@ -318,10 +320,19 @@ private HalResource addEmbeddedLicense(String licenseId) { try { License licenseById = licenseService.getLicenseById(licenseId); embeddedLicense.setFullname(licenseById.getFullname()); + embeddedLicense.setShortname(licenseId); Link licenseSelfLink = linkTo(UserController.class) .slash("api" + LicenseController.LICENSES_URL + "/" + licenseById.getId()).withSelfRel(); halLicense.add(licenseSelfLink); return halLicense; + } catch (ResourceNotFoundException rne) { + LOGGER.error("cannot create a self link for license with id" + licenseId); + embeddedLicense.setShortname(licenseId); + embeddedLicense.setOSIApproved(Quadratic.NA); + embeddedLicense.setFSFLibre(Quadratic.NA); + embeddedLicense.setChecked(false); + embeddedLicense.setFullname(null); + return halLicense; } catch (Exception e) { LOGGER.error("cannot create self link for license with id: " + licenseId); } From 728acb208c7673120d438043e74cb2447cae1c27 Mon Sep 17 00:00:00 2001 From: Smruti Prakash Sahoo Date: Thu, 30 Jun 2022 17:41:36 +0530 Subject: [PATCH 4/7] fix(export): Added missing ECC AL column and release vendor in project export Signed-off-by: Smruti Prakash Sahoo --- .../main/java/org/eclipse/sw360/exporter/ProjectExporter.java | 1 + .../main/java/org/eclipse/sw360/exporter/ReleaseExporter.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ProjectExporter.java b/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ProjectExporter.java index 92f94422ad..f7981c4939 100644 --- a/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ProjectExporter.java +++ b/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ProjectExporter.java @@ -19,6 +19,7 @@ import org.eclipse.sw360.datahandler.thrift.projects.Project; import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; import org.eclipse.sw360.datahandler.thrift.users.User; +import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.eclipse.sw360.exporter.helper.ExporterHelper; import org.eclipse.sw360.exporter.helper.ProjectHelper; import org.eclipse.sw360.exporter.helper.ReleaseHelper; diff --git a/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ReleaseExporter.java b/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ReleaseExporter.java index d9d3b4ec33..ef4c184d74 100644 --- a/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ReleaseExporter.java +++ b/libraries/exporters/src/main/java/org/eclipse/sw360/exporter/ReleaseExporter.java @@ -65,10 +65,10 @@ public class ReleaseExporter extends ExcelExporter { .add(VERSION) .add(CLEARING_STATE) .add(ECC_INFORMATION) + .add(VENDOR) .build(); public static final List ECC_IGNORE_FIELDS = ImmutableList.builder() - .add(AL) .add(MATERIAL_INDEX_NUMBER) .add(ASSESSOR_CONTACT_PERSON) .add(ECC_COMMENT) From bcdfad6b82e1ce2510b4694f5166aefdae310eae Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Thu, 23 Jun 2022 10:31:49 +0200 Subject: [PATCH 5/7] update(docker): Docker to use latest Ubuntu LTS - Move to Ubuntu Jammy and use maven package - Use docker compose now integrated to docker current release - Updated thrift version 0.16.0 - Use same install-thrift for action and docker Signed-off-by: Helio Chissini de Castro --- .github/workflows/githubactions.yml | 17 ++- .gitignore | 10 ++ Dockerfile | 111 ++++++++++-------- docker-compose.yml | 2 + docker_build.sh | 45 +++---- .../docker-config/download_dependencies.sh | 11 +- .../install_scripts/build_thrift.sh | 28 ----- .../install_scripts/configure_couchdb.sh | 29 ----- scripts/install-thrift.sh | 60 ++++------ scripts/patches/couchdb-lucene.patch | 98 ++++++++++++++++ sw360BackendRest.Dockerfile | 2 +- 11 files changed, 234 insertions(+), 179 deletions(-) delete mode 100755 scripts/docker-config/install_scripts/build_thrift.sh delete mode 100755 scripts/docker-config/install_scripts/configure_couchdb.sh create mode 100644 scripts/patches/couchdb-lucene.patch diff --git a/.github/workflows/githubactions.yml b/.github/workflows/githubactions.yml index 83ece61b31..aa1a5478c3 100644 --- a/.github/workflows/githubactions.yml +++ b/.github/workflows/githubactions.yml @@ -24,7 +24,7 @@ env: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 @@ -40,24 +40,23 @@ jobs: - name: Update properties with DB credentials run: | - sudo apt-get update -qq sudo sed -i 's/^couchdb.user\s*=/& '${COUCHDB_USER}'/' build-configuration/test-resources/couchdb-test.properties sudo sed -i 's/^couchdb.password\s*=/& '${COUCHDB_PASSWORD}'/' build-configuration/test-resources/couchdb-test.properties sudo mkdir /etc/sw360 sudo cp ./scripts/sw360BackendRestDockerConfig/etc_sw360/couchdb-test.properties /etc/sw360/ + - name: Prepare build environment + run: | + sudo apt-get update -qq + sudo apt-get install python3 python3-pip build-essential libboost-dev libboost-test-dev libboost-program-options-dev libevent-dev automake libtool flex bison pkg-config libssl-dev git + - name: Verify license headers run: | chmod +x .github/testForLicenseHeaders.sh bash .github/testForLicenseHeaders.sh - - name: Install Thrift and mkdocs + - name: Install Thrift run: | - sudo apt-get update - sudo apt-get install python3 - sudo apt-get install python3-pip - sudo pip3 install mkdocs - sudo pip3 install mkdocs-material chmod +x scripts/install-thrift.sh bash scripts/install-thrift.sh --no-cleanup @@ -81,7 +80,7 @@ jobs: - name: Create users and oauth client run: bash scripts/sw360BackendRestDockerConfig/scripts/createUserAndOauthClient.sh - - name: Run Client Itegration Test for Rest Api + - name: Run Client Integration Test for Rest Api run: | cd clients mvn clean install --no-transfer-progress -DRunRestIntegrationTest=true diff --git a/.gitignore b/.gitignore index d182626f77..15e323e5dd 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,13 @@ data config .tmp .env + +# Gradle generated files +bin/** +gen/** +.gradletasknamecache +.gradle/ +build/ +bin/ +tmp/** +tmp/**/* diff --git a/Dockerfile b/Dockerfile index 6847fcf065..6786049f90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,10 +9,22 @@ # SPDX-License-Identifier: EPL-2.0 # -FROM maven:3.8-eclipse-temurin-11 AS builder +FROM eclipse-temurin:11-jdk-jammy AS builder -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ +# Set versiona as arguments +ARG CLUCENE_VERSION=2.1.0 +ARG THRIFT_VERSION=0.16.0 +ARG MAVEN_VERSION=3.8.6 + +# Lets get dependencies as buildkit cached +ENV SW360_DEPS_DIR=/var/cache/deps +COPY ./scripts/docker-config/download_dependencies.sh /var/tmp/deps.sh +RUN --mount=type=cache,mode=0755,target=/var/cache/deps,sharing=locked \ + chmod +x /var/tmp/deps.sh \ + && /var/tmp/deps.sh + +RUN --mount=type=cache,mode=0755,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,mode=0755,target=/var/lib/apt,sharing=locked \ apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ automake \ @@ -31,9 +43,13 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ pkg-config \ procps \ wget \ + unzip \ + zip \ && rm -rf /var/lib/apt/lists/* -# Prepare proxy for maven +# Prepare maven from binary to avoid wrong java dependencies and proxy +RUN --mount=type=cache,mode=0755,target=/var/cache/deps \ + tar -xzf "/var/cache/deps/apache-maven-$MAVEN_VERSION-bin.tar.gz" --strip-components=1 -C /usr/local COPY scripts/docker-config/mvn-proxy-settings.xml /etc COPY scripts/docker-config/set_proxy.sh /usr/local/bin/setup_maven_proxy RUN chmod a+x /usr/local/bin/setup_maven_proxy @@ -42,15 +58,15 @@ RUN chmod a+x /usr/local/bin/setup_maven_proxy # Thrift FROM builder AS thriftbuild -ARG THRIFT_VERSION=0.14.0 +ARG BASEDIR="/build" +ARG THRIFT_VERSION=0.16.0 -COPY deps/thrift-*.tar.gz /deps/ -COPY ./scripts/docker-config/install_scripts/build_thrift.sh build_thrift.sh +COPY ./scripts/install-thrift.sh build_thrift.sh RUN --mount=type=tmpfs,target=/build \ - tar -xzf "deps/thrift-$THRIFT_VERSION.tar.gz" --strip-components=1 -C /build \ - && ./build_thrift.sh \ - && rm -rf /deps + --mount=type=cache,mode=0755,target=/var/cache/deps,sharing=locked \ + tar -xzf "/var/cache/deps/thrift-$THRIFT_VERSION.tar.gz" --strip-components=1 -C /build \ + && ./build_thrift.sh --tarball #-------------------------------------------------------------------------------------------------- # Couchdb-Lucene @@ -60,20 +76,21 @@ ARG CLUCENE_VERSION=2.1.0 WORKDIR /build -COPY deps/couchdb* /deps/ -COPY ./scripts/docker-config/couchdb-lucene.ini /deps - # Prepare source code -RUN --mount=type=tmpfs,target=/build \ - --mount=type=cache,target=/root/.m2,rw,sharing=locked \ - tar -C /build -xvf /deps/couchdb-lucene-$CLUCENE_VERSION.tar.gz --strip-components=1 \ - && patch -p1 < /deps/couchdb-lucene.patch \ - && cp /deps/couchdb-lucene.ini ./src/main/resources/couchdb-lucene.ini \ +COPY ./scripts/docker-config/couchdb-lucene.ini /var/tmp/couchdb-lucene.ini +COPY ./scripts/patches/couchdb-lucene.patch /var/tmp/couchdb-lucene.patch + +# Build CLucene +RUN --mount=type=cache,mode=0755,target=/var/cache/deps,sharing=locked \ + --mount=type=tmpfs,target=/build \ + --mount=type=cache,mode=0755,target=/root/.m2,rw,sharing=locked \ + tar -C /build -xf /var/cache/deps/couchdb-lucene-$CLUCENE_VERSION.tar.gz --strip-components=1 \ + && patch -p1 < /var/tmp/couchdb-lucene.patch \ + && cp /var/tmp/couchdb-lucene.ini src/main/resources/couchdb-lucene.ini \ && setup_maven_proxy \ - && mvn dependency:go-offline \ + && mvn dependency:go-offline -B \ && mvn install war:war \ - && cp ./target/*.war /couchdb-lucene.war \ - && rm -rf /deps + && cp ./target/*.war /couchdb-lucene.war #-------------------------------------------------------------------------------------------------- # SW360 @@ -81,45 +98,37 @@ RUN --mount=type=tmpfs,target=/build \ # So when decide to use as development, only this last stage # is triggered by buildkit images -FROM builder AS sw360build - -# Copy thrft from builder -COPY --from=thriftbuild /thrift-bin.tar.gz /deps/ -RUN tar xzf /deps/thrift-bin.tar.gz -C / +FROM thriftbuild AS sw360build # Install mkdocs to generate documentation -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - mkdocs \ +RUN --mount=type=cache,mode=0755,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,mode=0755,target=/var/lib/apt,sharing=locked \ + apt-get update -qq \ + && DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --no-install-recommends \ python3-pip \ python3-wheel \ && rm -rf /var/lib/apt/lists/* \ && pip install mkdocs-material -# Copy the exported sw360 directory -COPY deps/sw360.tar /deps/ +# Copy the sw360 directory +COPY . /build/sw360 -RUN --mount=type=tmpfs,target=/build \ - --mount=type=cache,target=/root/.m2,rw,sharing=locked \ - tar -C /build -xf /deps/sw360.tar \ - && cd /build/sw360 \ +RUN --mount=type=cache,mode=0755,target=/root/.m2,rw,sharing=locked \ + cd /build/sw360 \ && setup_maven_proxy \ - && mvn package \ + && mvn clean package \ -P deploy -Dtest=org.eclipse.sw360.rest.resourceserver.restdocs.* \ -DfailIfNoTests=false \ -Dbase.deploy.dir=. \ -Dliferay.deploy.dir=/sw360_deploy \ -Dbackend.deploy.dir=/sw360_tomcat_webapps \ -Drest.deploy.dir=/sw360_tomcat_webapps \ - -Dhelp-docs=true \ - && rm -rf /deps + -Dhelp-docs=true #-------------------------------------------------------------------------------------------------- # Runtime image # We need use JDK, JRE is not enough as Liferay do runtime changes and require javac -FROM eclipse-temurin:11-jdk-focal +FROM eclipse-temurin:11-jdk-jammy WORKDIR /app/ @@ -129,9 +138,10 @@ ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US:en ENV LC_ALL=en_US.UTF-8 -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - apt-get update \ +RUN --mount=type=cache,mode=0755,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,mode=0755,target=/var/lib/apt,sharing=locked \ + --mount=type=cache,mode=0755,target=/var/cache/deps,sharing=locked \ + apt-get update -qq \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ ca-certificates \ curl \ @@ -151,9 +161,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ zip \ && rm -rf /var/lib/apt/lists/* -COPY deps/jars/* /deps/jars/ -COPY deps/liferay-ce* /deps/ - COPY --from=thriftbuild /thrift-bin.tar.gz . RUN tar xzf thrift-bin.tar.gz -C / \ && rm thrift-bin.tar.gz @@ -182,12 +189,12 @@ RUN echo "$USERNAME ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME \ # Unpack liferay as sw360 and link current tomcat version # to tomcat to make future proof updates -RUN mkdir sw360 \ - && tar xzf /deps/$LIFERAY_SOURCE -C $USERNAME --strip-components=1 \ - && cp /deps/jars/* sw360/deploy \ +RUN --mount=type=cache,mode=0755,target=/var/cache/deps,sharing=locked \ + mkdir sw360 \ + && tar xzf /var/cache/deps/$LIFERAY_SOURCE -C $USERNAME --strip-components=1 \ + && cp /var/cache/deps/jars/* sw360/deploy \ && chown -R $USERNAME:$USERNAME sw360 \ - && ln -s /app/sw360/tomcat-* /app/sw360/tomcat \ - && rm -rf /deps + && ln -s /app/sw360/tomcat-* /app/sw360/tomcat COPY --chown=$USERNAME:$USERNAME --from=sw360build /sw360_deploy/* /app/sw360/deploy COPY --chown=$USERNAME:$USERNAME --from=sw360build /sw360_tomcat_webapps/* /app/sw360/tomcat/webapps/ diff --git a/docker-compose.yml b/docker-compose.yml index 892339715f..513aeb2de1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ version: '3.8' services: sw360: image: 'sw360:latest' + restart: unless-stopped build: context: . args: @@ -49,6 +50,7 @@ services: couchdb: image: couchdb + restart: unless-stopped environment: - COUCHDB_USER=admin - COUCHDB_PASSWORD=password diff --git a/docker_build.sh b/docker_build.sh index 58bb9d9099..0715928782 100755 --- a/docker_build.sh +++ b/docker_build.sh @@ -15,13 +15,12 @@ set -e -GIT_ROOT=$(git rev-parse --show-toplevel) - -# Download dependencies outside container -"$GIT_ROOT"/scripts/docker-config/download_dependencies.sh +# Set default versions +CLUCENE_VERSION=${CLUCENE_VERSION:-2.1.0} +THRIFT_VERSION=${THRIFT_VERSION:-0.16.0} +MAVEN_VERSION=${MAVEN_VERSION:-3.8.6} -# To avoid excessive copy, we will export the git archive of the sources to deps -git archive --output=deps/sw360.tar --format=tar --prefix=sw360/ HEAD +GIT_ROOT=$(git rev-parse --show-toplevel) COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 @@ -29,27 +28,33 @@ export DOCKER_BUILDKIT COMPOSE_DOCKER_CLI_BUILD usage() { echo "Usage:" - echo "-v Verbose build" + echo "--help This messaqge" + echo "--verbose Verbose build" + echo "--no-cache Invalidate buildkit cache" exit 0; } -while getopts "hv" arg; do - case $arg in - h) - usage - ;; - v) - docker_verbose="--progress=plain" - ;; - *) - ;; - esac +for arg in "$@"; do + if [ "$arg" == "--help" ]; then + usage + elif [ "$arg" == "--verbose" ]; then + docker_verbose="--progress=plain" + elif [ "$arg" == "--no-cache" ]; then + docker_no_cache="--no-cache" + else + echo "Unsupported parameter: $arg" + usage + fi + shift done #shellcheck disable=SC2086 -docker-compose \ +docker compose \ --file "$GIT_ROOT"/docker-compose.yml \ build \ --build-arg BUILDKIT_INLINE_CACHE=1 \ + --build-arg CLUCENE_VERSION="$CLUCENE_VERSION" \ + --build-arg THRIFT_VERSION="$THRIFT_VERSION" \ + --build-arg MAVEN_VERSION="$MAVEN_VERSION" \ $docker_verbose \ - "$@" + $docker_no_cache diff --git a/scripts/docker-config/download_dependencies.sh b/scripts/docker-config/download_dependencies.sh index 658962acbc..2f0df8a916 100755 --- a/scripts/docker-config/download_dependencies.sh +++ b/scripts/docker-config/download_dependencies.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash -x # ----------------------------------------------------------------------------- # Copyright Siemens AG, 2020. Part of the SW360 Portal Project. @@ -28,15 +28,14 @@ jar_dependencies=( https://search.maven.org/remotecontent?filepath=com/fasterxml/jackson/core/jackson-core/2.13.2/jackson-core-2.13.2.jar https://search.maven.org/remotecontent?filepath=com/fasterxml/jackson/core/jackson-databind/2.13.2.2/jackson-databind-2.13.2.2.jar https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar - https://repo1.maven.org/maven2/org/apache/thrift/libthrift/0.14.0/libthrift-0.14.0.jar + https://repo1.maven.org/maven2/org/apache/thrift/libthrift/"$THRIFT_VERSION"/libthrift-"$THRIFT_VERSION".jar ) dependencies=( https://github.com/liferay/liferay-portal/releases/download/7.3.4-ga5/liferay-ce-portal-tomcat-7.3.4-ga5-20200811154319029.tar.gz - https://sourceforge.net/projects/lportal/files/Liferay%20Portal/7.3.4%20GA5/liferay-ce-portal-tomcat-7.3.4-ga5-20200811154319029.tar.gz - http://archive.apache.org/dist/thrift/0.14.0/thrift-0.14.0.tar.gz - https://github.com/rnewson/couchdb-lucene/archive/v2.1.0.tar.gz - https://raw.githubusercontent.com/sw360/sw360vagrant/master/shared/couchdb-lucene.patch + https://github.com/rnewson/couchdb-lucene/archive/v"$CLUCENE_VERSION".tar.gz + http://archive.apache.org/dist/thrift/0.16.0/thrift-"$THRIFT_VERSION".tar.gz + https://dlcdn.apache.org/maven/maven-3/"$MAVEN_VERSION"/binaries/apache-maven-"$MAVEN_VERSION"-bin.tar.gz ) download_dependency() { diff --git a/scripts/docker-config/install_scripts/build_thrift.sh b/scripts/docker-config/install_scripts/build_thrift.sh deleted file mode 100755 index fa5655a857..0000000000 --- a/scripts/docker-config/install_scripts/build_thrift.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# -# Copyright Siemens AG, 2020. Part of the SW360 Portal Project. -# Copyright BMW CarIT GmbH, 2021. -# -# This program and the accompanying materials are made -# available under the terms of the Eclipse Public License 2.0 -# which is available at https://www.eclipse.org/legal/epl-2.0/ -# -# SPDX-License-Identifier: EPL-2.0 -# - -cd /build || exit 1 - -if [[ ! -f "./Makefile" ]]; then - ./configure --without-java --without-cpp --without-qt4 --without-c_glib --without-csharp --without-erlang \ - --without-perl --without-php --without-php_extension --without-python --without-py3 --without-ruby \ - --without-haskell --without-go --without-d --without-haskell --without-php --without-ruby \ - --without-python --without-erlang --without-perl --without-c_sharp --without-d --without-php \ - --without-go --without-lua --without-nodejs --without-cl --without-dotnetcore --without-swift --without-rs || exit 1 -fi - -make -j$(nproc) && make install - -# Pack fo next docker stage -make DESTDIR="$PWD"/thrift-binary install -cd thrift-binary || exit 1 -tar cfz /thrift-bin.tar.gz . diff --git a/scripts/docker-config/install_scripts/configure_couchdb.sh b/scripts/docker-config/install_scripts/configure_couchdb.sh deleted file mode 100755 index 0fedf81fda..0000000000 --- a/scripts/docker-config/install_scripts/configure_couchdb.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# ----------------------------------------------------------------------------- -# Copyright Siemens AG, 2020. Part of the SW360 Portal Project. -# -# This program and the accompanying materials are made -# available under the terms of the Eclipse Public License 2.0 -# which is available at https://www.eclipse.org/legal/epl-2.0/ -# -# SPDX-License-Identifier: EPL-2.0 -# -# This script installs couchdb, configures couchdb-lucene -# and accessibility from host machine. -# ----------------------------------------------------------------------------- - -COUCHDB_ADMIN="${COUCHDB_ADMIN:-admin}" -COUCHDB_PASSWD="${COUCHDB_PASSWD:-password}" - -configure_sw360() { - cat < /opt/couchdb/etc/local.d/sw360.ini -[admins] -${COUCHDB_ADMIN} = ${COUCHDB_PASSWD} - -[chttpd] -port = 5984 -bind_address = 0.0.0.0 -EOF -} - -configure_sw360 diff --git a/scripts/install-thrift.sh b/scripts/install-thrift.sh index 1f4a8aef8e..9ed045c0c5 100755 --- a/scripts/install-thrift.sh +++ b/scripts/install-thrift.sh @@ -18,10 +18,11 @@ # ----------------------------------------------------------------------------- set -e -CURRENT_THRIFT_VERSION=0.14.0 -BASEDIR="/tmp" +BASEDIR="${BASEDIR:-/tmp}" CLEANUP=true +TARBALL=false +THRIFT_VERSION=${THRIFT_VERSION:-0.16.0} UNINSTALL=false has() { type "$1" &> /dev/null; } @@ -34,22 +35,14 @@ processThrift() { if [[ ! -d "$BUILDDIR" ]]; then echo "-[shell provisioning] Extracting thrift" if [ -e "/vagrant_shared/packages/thrift-$VERSION.tar.gz" ]; then - tar -xzf "/vagrant_shared/packages/thrift-$VERSION.tar.gz" -C $BASEDIR + tar -xzf "/vagrant_shared/packages/thrift-$VERSION.tar.gz" -C "$BASEDIR" + elif [ -e "/deps/thrift-$VERSION.tar.gz" ]; then + tar -xzf "/deps/thrift-$VERSION.tar.gz" -C "$BASEDIR" else - if has "apt-get" ; then - $SUDO_CMD apt-get update - $SUDO_CMD apt-get install -y curl - elif has "apk" ; then - $SUDO_CMD apk --update add curl - else - echo "no supported package manager found" - exit 1 - fi - TGZ="${BASEDIR}/thrift-$VERSION.tar.gz" - curl -z $TGZ -o $TGZ http://archive.apache.org/dist/thrift/$VERSION/thrift-$VERSION.tar.gz - tar -xzf $TGZ -C $BASEDIR + curl -z "$TGZ" -o "$TGZ" "http://archive.apache.org/dist/thrift/$VERSION/thrift-$VERSION.tar.gz" + tar -xzf "$TGZ" -C "$BASEDIR" [[ $CLEANUP ]] && rm "${BASEDIR}/thrift-$VERSION.tar.gz" fi @@ -59,16 +52,6 @@ processThrift() { if [[ ! -f "./compiler/cpp/thrift" ]]; then echo "-[shell provisioning] Installing dependencies of thrift" - if has "apt-get" ; then - $SUDO_CMD apt-get update - $SUDO_CMD apt-get install -y build-essential libboost-dev libboost-test-dev libboost-program-options-dev libevent-dev automake libtool flex bison pkg-config g++ libssl-dev - elif has "apk" ; then - $SUDO_CMD apk --update add g++ make apache-ant libtool automake autoconf bison flex - else - echo "no supported package manager found" - exit 1 - fi - echo "-[shell provisioning] Building thrift" if [[ ! -f "./Makefile" ]]; then ./configure --without-java --without-cpp --without-qt4 --without-c_glib --without-csharp --without-erlang \ @@ -78,38 +61,45 @@ processThrift() { --without-go --without-lua --without-nodejs --without-cl --without-dotnetcore --without-swift --without-rs fi if [ "$1" == true ]; then - make + # shellcheck disable=SC2046 + make -j$(nproc) fi fi echo "-[shell provisioning] Executing make $2 on thrift" - $SUDO_CMD make $2 + $SUDO_CMD make "$2" + + if [ "$TARBALL" = true ]; then + make DESTDIR="$PWD"/thrift-binary install + cd thrift-binary || exit 1 + tar cfz /thrift-bin.tar.gz . + fi - if [[ $CLEANUP ]]; then + if [ "$CLEANUP" = true ]; then $SUDO_CMD rm -rf "$BUILDDIR" fi } installThrift() { if has "thrift"; then - if thrift --version | grep -q "$CURRENT_THRIFT_VERSION"; then + if thrift --version | grep -q "$THRIFT_VERSION"; then echo "thrift is already installed at $(which thrift)" exit 0 else - echo "thrift is already installed but does not have the correct version: $CURRENT_THRIFT_VERSION" + echo "thrift is already installed but does not have the correct version: $THRIFT_VERSION" echo "Use '$0 --uninstall' first to remove the incorrect version and then try again." exit 1 fi fi - processThrift true "install" $CURRENT_THRIFT_VERSION + processThrift true "install" "$THRIFT_VERSION" } uninstallThrift() { if has "thrift"; then VERSION=$(thrift --version | cut -f 3 -d" ") echo "Uninstalling thrift version $VERSION" - processThrift false "uninstall" $VERSION + processThrift false "uninstall" "$VERSION" else echo "thrift not installed on this machine." exit 1 @@ -118,10 +108,12 @@ uninstallThrift() { for arg in "$@" do - if [ $arg == "--no-cleanup" ]; then + if [ "$arg" == "--no-cleanup" ]; then CLEANUP=false - elif [ $arg == "--uninstall" ]; then + elif [ "$arg" == "--uninstall" ]; then UNINSTALL=true + elif [ "$arg" == "--tarball" ]; then + TARBALL=true else echo "Unsupported parameter: $arg" echo "Usage: $0 [--no-cleanup] [--uninstall]" diff --git a/scripts/patches/couchdb-lucene.patch b/scripts/patches/couchdb-lucene.patch new file mode 100644 index 0000000000..dfab4e92f3 --- /dev/null +++ b/scripts/patches/couchdb-lucene.patch @@ -0,0 +1,98 @@ +# Copyright Siemens AG, 2013-2015. Part of the SW360 Portal Project. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. +# +# SPDX-License-Identifier: EPL-2.0 +# +From a332d09f9045ce3976d71823ebd9a4fe4a736cb3 Mon Sep 17 00:00:00 2001 +From: Birgit Heydenreich +Date: Sat, 7 Nov 2015 15:31:24 +0100 +Subject: [PATCH] patched with context pull request jalpedersen + + +--- + .../java/com/github/rnewson/couchdb/lucene/LuceneServlet.java | 9 ++++++--- + src/main/java/com/github/rnewson/couchdb/lucene/PathParts.java | 4 +++- + .../com/github/rnewson/couchdb/lucene/util/ServletUtils.java | 6 ++++++ + 3 files changed, 15 insertions(+), 4 deletions(-) + +diff --git a/src/main/java/com/github/rnewson/couchdb/lucene/LuceneServlet.java b/src/main/java/com/github/rnewson/couchdb/lucene/LuceneServlet.java +index ecb200d..9426e58 100644 +--- a/src/main/java/com/github/rnewson/couchdb/lucene/LuceneServlet.java ++++ b/src/main/java/com/github/rnewson/couchdb/lucene/LuceneServlet.java +@@ -41,6 +41,8 @@ + import java.io.IOException; + import java.util.*; + ++import static com.github.rnewson.couchdb.lucene.util.ServletUtils.getUri; ++ + public final class LuceneServlet extends HttpServlet { + + private static final Logger LOG = Logger.getLogger(LuceneServlet.class); +@@ -176,7 +178,7 @@ protected void doGet(final HttpServletRequest req, + + private void doGetInternal(final HttpServletRequest req, final HttpServletResponse resp) + throws ServletException, IOException, JSONException { +- switch (StringUtils.countMatches(req.getRequestURI(), "/")) { ++ switch (StringUtils.countMatches(getUri(req), "/")) { + case 1: + handleWelcomeReq(req, resp); + return; +@@ -211,9 +213,9 @@ protected void doPost(final HttpServletRequest req, + + private void doPostInternal(final HttpServletRequest req, final HttpServletResponse resp) + throws IOException, JSONException { +- switch (StringUtils.countMatches(req.getRequestURI(), "/")) { ++ switch (StringUtils.countMatches(getUri(req), "/")) { + case 3: +- if (req.getPathInfo().endsWith("/_cleanup")) { ++ if (req.getRequestURI().endsWith("/_cleanup")) { + cleanup(req, resp); + return; + } +@@ -235,4 +237,5 @@ private void doPostInternal(final HttpServletRequest req, final HttpServletRespo + ServletUtils.sendJsonError(req, resp, 400, "bad_request"); + } + ++ + } +diff --git a/src/main/java/com/github/rnewson/couchdb/lucene/PathParts.java b/src/main/java/com/github/rnewson/couchdb/lucene/PathParts.java +index d73222d..b4bd898 100644 +--- a/src/main/java/com/github/rnewson/couchdb/lucene/PathParts.java ++++ b/src/main/java/com/github/rnewson/couchdb/lucene/PathParts.java +@@ -20,6 +20,8 @@ + import java.util.regex.Matcher; + import java.util.regex.Pattern; + ++import static com.github.rnewson.couchdb.lucene.util.ServletUtils.getUri; ++ + public class PathParts { + + private static final Pattern QUERY_REGEX = Pattern +@@ -31,7 +33,7 @@ + private Matcher matcher; + + public PathParts(final HttpServletRequest req) { +- this(req.getRequestURI()); ++ this(getUri(req)); + } + + public PathParts(final String path) { +diff --git a/src/main/java/com/github/rnewson/couchdb/lucene/util/ServletUtils.java b/src/main/java/com/github/rnewson/couchdb/lucene/util/ServletUtils.java +index de0da0f..2c57695 100644 +--- a/src/main/java/com/github/rnewson/couchdb/lucene/util/ServletUtils.java ++++ b/src/main/java/com/github/rnewson/couchdb/lucene/util/ServletUtils.java +@@ -102,4 +102,10 @@ public static void sendJsonSuccess(final HttpServletRequest req, final HttpServl + } + } + ++ //Strip of context part of URI ++ public static String getUri(HttpServletRequest request) { ++ //Strip of context path if present ++ return request.getRequestURI().substring(request.getContextPath().length()); ++ } ++ + } diff --git a/sw360BackendRest.Dockerfile b/sw360BackendRest.Dockerfile index aea4632e94..5043decb81 100644 --- a/sw360BackendRest.Dockerfile +++ b/sw360BackendRest.Dockerfile @@ -8,7 +8,7 @@ # SPDX-License-Identifier: EPL-2.0 # -FROM tomcat:9.0.50-jdk11-openjdk-slim +FROM tomcat:10.1-jdk11-temurin-jammy COPY ./scripts/sw360BackendRestDockerConfig/etc_sw360/ /etc/sw360/ From 1bef35d37bc03482c272acb5a8bd7ad283b14f04 Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Thu, 30 Jun 2022 07:58:11 +0200 Subject: [PATCH 6/7] update(ghactions): Improve gh actions process - Use new a actions/v3 - Use new Ubuntu LTS 22.04 ( Jammy ) - Move external java setup to temurin package based to be aligned with docker - Install all necessary packages in a single step Signed-off-by: Helio Chissini de Castro --- .github/workflows/githubactions.yml | 20 +++++++++++--------- sw360BackendRest.Dockerfile | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/githubactions.yml b/.github/workflows/githubactions.yml index aa1a5478c3..a6710aae9f 100644 --- a/.github/workflows/githubactions.yml +++ b/.github/workflows/githubactions.yml @@ -27,12 +27,8 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v2 - with: - java-version: '11' - distribution: 'adopt' + - uses: actions/checkout@v3 + - name: Setup CouchDB uses: iamssen/couchdb-github-action@master with: @@ -42,13 +38,19 @@ jobs: run: | sudo sed -i 's/^couchdb.user\s*=/& '${COUCHDB_USER}'/' build-configuration/test-resources/couchdb-test.properties sudo sed -i 's/^couchdb.password\s*=/& '${COUCHDB_PASSWORD}'/' build-configuration/test-resources/couchdb-test.properties - sudo mkdir /etc/sw360 + sudo mkdir -p /etc/sw360 sudo cp ./scripts/sw360BackendRestDockerConfig/etc_sw360/couchdb-test.properties /etc/sw360/ + - name: Set up JDK 11 + run: | + wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo tee /usr/share/keyrings/adoptium.asc + echo "deb [signed-by=/usr/share/keyrings/adoptium.asc] https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | sudo tee /etc/apt/sources.list.d/adoptium.list + - name: Prepare build environment run: | sudo apt-get update -qq - sudo apt-get install python3 python3-pip build-essential libboost-dev libboost-test-dev libboost-program-options-dev libevent-dev automake libtool flex bison pkg-config libssl-dev git + sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq python3-pip build-essential libboost-dev libboost-test-dev libboost-program-options-dev libevent-dev automake libtool flex bison pkg-config libssl-dev git temurin-11-jdk maven + pip install mkdocs mkdocs-material - name: Verify license headers run: | @@ -61,7 +63,7 @@ jobs: bash scripts/install-thrift.sh --no-cleanup - name: Build SW360 - run: mvn clean package --no-transfer-progress -P deploy -Dhelp-docs=true -Dbase.deploy.dir=. -Dliferay.deploy.dir=/home/runner/work/sw360/sw360/deploy -Dbackend.deploy.dir=/home/runner/work/sw360/sw360/webapps -Drest.deploy.dir=/home/runner/work/sw360/sw360/webapps -DRunComponentVisibilityRestrictionTest=false -DRunPrivateProjectAccessTest=false + run: mvn clean package --no-transfer-progress -P deploy -Dhelp-docs=true -Dbase.deploy.dir=. -Dliferay.deploy.dir=${PWD}/deploy -Dbackend.deploy.dir=${PWD}/deploy/webapps -Drest.deploy.dir=${PWD}/deploy/webapps -DRunComponentVisibilityRestrictionTest=false -DRunPrivateProjectAccessTest=false - name: Run PrivateProjectAccessTest run: | diff --git a/sw360BackendRest.Dockerfile b/sw360BackendRest.Dockerfile index 5043decb81..b72858a6c7 100644 --- a/sw360BackendRest.Dockerfile +++ b/sw360BackendRest.Dockerfile @@ -8,8 +8,8 @@ # SPDX-License-Identifier: EPL-2.0 # -FROM tomcat:10.1-jdk11-temurin-jammy +FROM tomcat:9-jdk11-temurin-jammy COPY ./scripts/sw360BackendRestDockerConfig/etc_sw360/ /etc/sw360/ -COPY ./webapps/* /usr/local/tomcat/webapps/ +COPY ./deploy/webapps/* /usr/local/tomcat/webapps/ From 0d0de03c7b205f085c75c8c49997dc3a4c4a9479 Mon Sep 17 00:00:00 2001 From: Abdul Kapti Date: Thu, 30 Jun 2022 12:11:36 +0530 Subject: [PATCH 7/7] feat(ReleaseUi):Display AssessmentSummary info from CLi in Release details page Signed-off-by: Abdul Kapti --- .../sw360/licenseinfo/parsers/CLIParser.java | 19 +++ .../sw360/portal/common/PortalConstants.java | 1 + .../portlets/components/ComponentPortlet.java | 50 +++++++ .../resources/css/components/_table.scss | 4 + .../META-INF/resources/css/main.scss | 16 +-- .../includes/releases/clearingDetails.jspf | 123 +++++++++++++++++- .../resources/content/Language.properties | 3 + .../resources/content/Language_ja.properties | 3 + .../resources/content/Language_vi.properties | 3 + .../src/main/thrift/licenseinfo.thrift | 3 +- 10 files changed, 214 insertions(+), 11 deletions(-) diff --git a/backend/src/src-licenseinfo/src/main/java/org/eclipse/sw360/licenseinfo/parsers/CLIParser.java b/backend/src/src-licenseinfo/src/main/java/org/eclipse/sw360/licenseinfo/parsers/CLIParser.java index 6faa04bf20..7a48a6450e 100644 --- a/backend/src/src-licenseinfo/src/main/java/org/eclipse/sw360/licenseinfo/parsers/CLIParser.java +++ b/backend/src/src-licenseinfo/src/main/java/org/eclipse/sw360/licenseinfo/parsers/CLIParser.java @@ -57,6 +57,7 @@ public class CLIParser extends AbstractCLIParser { private static final String COPYRIGHTS_XPATH = "/ComponentLicenseInformation/Copyright/Content"; private static final String LICENSES_XPATH = "/ComponentLicenseInformation/License"; private static final String OBLIGATIONS_XPATH = "/ComponentLicenseInformation/Obligation"; + private static final String ASSESSMENT_SUMMARY_XPATH = "/ComponentLicenseInformation/AssessmentSummary"; private static final String CLI_ROOT_ELEMENT_NAME = "ComponentLicenseInformation"; private static final String CLI_ROOT_XPATH = "/ComponentLicenseInformation"; private static final String CLI_ROOT_ELEMENT_NAMESPACE = null; @@ -130,6 +131,7 @@ private List getLicenseInfosDelegated(Attachment a } licenseInfo.setLicenseNamesWithTexts(getLicenseNameWithTexts(doc, includeFilesHash)); + licenseInfo.setAssessmentSummary(getAssessmentSummary(doc)); licenseInfo.setSha1Hash(getSha1Hash(doc)); licenseInfo.setComponentName(getComponent(doc)); @@ -154,6 +156,23 @@ private Set getLicenseNameWithTexts(Document doc, boolean i return nodeListToLicenseNamesWithTextsSet(licenseNodes, includeFilesHash); } + private Map getAssessmentSummary(Document doc) throws XPathExpressionException { + NodeList assessmentSummaryList = getNodeListByXpath(doc, ASSESSMENT_SUMMARY_XPATH); + Map assessmentSummaryMap = new HashMap(); + if (assessmentSummaryList.getLength() == 1) { + Node node = assessmentSummaryList.item(0); + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + String nodeName = childNodes.item(i).getNodeName(); + String textContent = childNodes.item(i).getTextContent(); + assessmentSummaryMap.put(nodeName, textContent); + } + } else { + log.error("AssessmentSummary not found in CLI!"); + } + return assessmentSummaryMap; + } + private Set getCopyrights(Document doc) throws XPathExpressionException { NodeList copyrightNodes = getNodeListByXpath(doc, COPYRIGHTS_XPATH); return nodeListToStringSet(copyrightNodes); diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java index 8250bcdf89..c628411a41 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/common/PortalConstants.java @@ -489,6 +489,7 @@ public class PortalConstants { public static final String DOWNLOAD_LICENSE_BACKUP = "DownloadLicenseBackup"; public static final String LOAD_SPDX_LICENSE_INFO = "LoadSpdxLicenseInfo"; public static final String WRITE_SPDX_LICENSE_INFO_INTO_RELEASE = "WriteSpdxLicenseInfoIntoRelease"; + public static final String LOAD_ASSESSMENT_SUMMARY_INFO = "LoadAssessmentSummaryInfo"; // linked projects and releases actions public static final String LINKED_OBJECTS_PREFIX = "load_linked_"; diff --git a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java index 0120fcbdb3..5de8a27343 100644 --- a/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java +++ b/frontend/sw360-portlet/src/main/java/org/eclipse/sw360/portal/portlets/components/ComponentPortlet.java @@ -44,10 +44,12 @@ import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentType; import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentService; import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentUsage; +import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus; import org.eclipse.sw360.datahandler.thrift.components.*; import org.eclipse.sw360.datahandler.thrift.cvesearch.CveSearchService; import org.eclipse.sw360.datahandler.thrift.cvesearch.VulnerabilityUpdateStatus; import org.eclipse.sw360.datahandler.thrift.fossology.FossologyService; +import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseInfo; import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseInfoParsingResult; import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseInfoService; import org.eclipse.sw360.datahandler.thrift.licenseinfo.LicenseNameWithText; @@ -227,6 +229,8 @@ public void serveResource(ResourceRequest request, ResourceResponse response) th linkReleaseToProject(request, response); } else if (PortalConstants.LOAD_SPDX_LICENSE_INFO.equals(action)) { loadSpdxLicenseInfo(request, response); + } else if (PortalConstants.LOAD_ASSESSMENT_SUMMARY_INFO.equals(action)) { + loadAssessmentSummaryInfo(request, response); } else if (PortalConstants.WRITE_SPDX_LICENSE_INFO_INTO_RELEASE.equals(action)) { writeSpdxLicenseInfoIntoRelease(request, response); } else if (PortalConstants.IMPORT_BOM.equals(action)) { @@ -766,6 +770,52 @@ private void loadSpdxLicenseInfo(ResourceRequest request, ResourceResponse respo } } + private void loadAssessmentSummaryInfo(ResourceRequest request, ResourceResponse response) { + ResourceBundle resourceBundle = ResourceBundleUtil.getBundle("content.Language", request.getLocale(), getClass()); + + User user = UserCacheHolder.getUserFromRequest(request); + String releaseId = request.getParameter(PortalConstants.RELEASE_ID); + String attachmentContentId = request.getParameter(PortalConstants.ATTACHMENT_ID); + + ComponentService.Iface componentClient = thriftClients.makeComponentClient(); + LicenseInfoService.Iface licenseInfoClient = thriftClients.makeLicenseInfoClient(); + Map assessmentSummaryMap = new HashMap<>(); + + try { + Release release = componentClient.getReleaseById(releaseId, user); + List licenseInfoResult = licenseInfoClient.getLicenseInfoForAttachment(release, + attachmentContentId, true, user); + if (CommonUtils.isNotEmpty(licenseInfoResult) && Objects.nonNull(licenseInfoResult.get(0).getLicenseInfo())) { + assessmentSummaryMap = licenseInfoResult.get(0).getLicenseInfo().getAssessmentSummary(); + } + } catch (TException e) { + log.error("Cannot retrieve license information for attachment id " + attachmentContentId + " in release " + + releaseId + ".", e); + response.setProperty("statusText", "Cannot retrieve license information for CLI"); + response.setStatus(500); + } + try { + JsonGenerator jsonGenerator = JSON_FACTORY.createGenerator(response.getWriter()); + jsonGenerator.writeStartObject(); + if (CommonUtils.isNullOrEmptyMap(assessmentSummaryMap)) { + jsonGenerator.writeStringField("status", "failure"); + jsonGenerator.writeStringField("msg", LanguageUtil.get(resourceBundle,"assessment.summary.information.is.not.present.in.CLI.file")); + } else { + for (Map.Entry entry : assessmentSummaryMap.entrySet()) { + jsonGenerator.writeStringField(entry.getKey(), entry.getValue()); + } + jsonGenerator.writeStringField("status", "success"); + } + jsonGenerator.writeEndObject(); + jsonGenerator.close(); + } catch (IOException | RuntimeException e) { + log.error("Cannot write JSON response for attachment id " + attachmentContentId + " in release " + releaseId + + ".", e); + response.setProperty("statusText", "Cannot retrieve license information for CLI"); + response.setStatus(500); + } + } + private void writeSpdxLicenseInfoIntoRelease(ResourceRequest request, ResourceResponse response) { User user = UserCacheHolder.getUserFromRequest(request); String releaseId = request.getParameter(PortalConstants.RELEASE_ID); diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/components/_table.scss b/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/components/_table.scss index 4a8016e403..091ea7ec50 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/components/_table.scss +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/components/_table.scss @@ -88,6 +88,10 @@ table { } } + td.comment-text { + white-space: pre-wrap; + } + &.aligned-top td { vertical-align: top; } diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/main.scss b/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/main.scss index 9e787d71b2..3b54b30197 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/main.scss +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/css/main.scss @@ -191,6 +191,14 @@ } table { + thead.cursor { + cursor: pointer; + } + td { + svg.cursor { + cursor: pointer; + } + } div.actions { display: flex; justify-content: space-evenly; @@ -221,14 +229,6 @@ } } - table { - td { - svg.cursor { - cursor: pointer; - } - } - } - .stateBox { height: 25px; padding: 0 4px; diff --git a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/releases/clearingDetails.jspf b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/releases/clearingDetails.jspf index 59cf6acad7..f0baaf0a8d 100644 --- a/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/releases/clearingDetails.jspf +++ b/frontend/sw360-portlet/src/main/resources/META-INF/resources/html/components/includes/releases/clearingDetails.jspf @@ -12,6 +12,10 @@ + + + + @@ -20,6 +24,10 @@ + + + + - + + + + + + + + + + + + + + + + +
    class="actions d-inline" > @@ -60,12 +76,76 @@
    - +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    + + + + + + ! + + + ! + + + ! + + +
    General Assessment
    Critical Files Found
    Dependency Notes
    Export Restrictions Found
    Usage Restrictions Found
    Additional Notes
    + + @@ -245,6 +325,45 @@ displayLicenseToSrcMapping(attId, attName, licenseName); }); + $(".showAssessmentSummaryBtn").on('click', function(event) { + button.wait(".showAssessmentSummaryBtn") + loadCliAssessmentSummary($(event.currentTarget)); + }); + + function loadCliAssessmentSummary($btn) { + var tableData = $('#assessmentSummary').data(), + rawUrl = tableData.loadAssessmentSummaryInfoUrl, + url = Liferay.PortletURL.createURL(rawUrl), + attachmentId = tableData.approvedAttachmentContentId ? tableData.approvedAttachmentContentId : tableData.attachmentContentId; + + $.ajax({ + type: 'GET', + data: { + "<%=PortalConstants.ATTACHMENT_ID%>": attachmentId + }, + dataType: 'json', + url: url.toString(), + }).done(function(result) { + if (!result || result.length == 0 || Object.getOwnPropertyNames(result).length == 0) { + $("tbody.btnBody tr td").html('Assessment Summary not found in CLI!'); + } else if (result.status === "success") { + $('tbody.dataBody > tr').each(function(index, tr) { + let key = $(tr).attr("data-key"), + val = result[key]; + $(tr).find("td:eq(1)").html(val); + }); + $("tbody.btnBody").addClass("d-none"); + $("tbody.dataBody").removeClass("d-none"); + } else if (result.status === "failure") { + $("tbody.btnBody tr td").html(result.msg); + } + }).fail(function(error) { + $("tbody.btnBody tr td span.alert").remove(); + $("tbody.btnBody tr td").append(""+error.status + ": "+ error.statusText + "") + }); + button.finish(".showAssessmentSummaryBtn"); + } + function displayLicenseToSrcMapping(attId, attName, licenseName) { let data, list = $('
      '); diff --git a/frontend/sw360-portlet/src/main/resources/content/Language.properties b/frontend/sw360-portlet/src/main/resources/content/Language.properties index 94343286bf..0001d9164d 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language.properties @@ -67,6 +67,8 @@ approved.releases.total.number.of.releases=approved releases / total number of r a.project.with.the.same.name.and.version.already.exists=A project with the same name and version already exists. a.release.with.the.same.name.and.version.already.exists=A release with the same name and version already exists. assessment.date=Assessment Date +assessment.summary=Assessment Summary +assessment.summary.information.is.not.present.in.CLI.file=Assessment Summary information is not present in CLI file assessor.contact.person=Assessor Contact Person assessor.department=Assessor Department assigned.external.component.ids=Assigned external component ids @@ -820,6 +822,7 @@ more.info=More Info more.than.1000.documents.affected.the.merge.operation.might.time.out.in.consequence.only.some.of=More than 1000 documents affected. The merge operation might time out. In consequence only some of multiple.approved.cli.are.found.in.the.release=Multiple approved CLI are found in release multiple.attachments.with.same.name.or.content.cannot.be.present.in.attachment.list=Multiple attachments with same name or content cannot be present in attachment list. +multiple.cli.are.found.in.the.release=Multiple CLI are found in release multiple.isr.are.found.in.the.release=Multiple ISR (Initial Scan Reports) are found in release my.components=My Components my.projects=My Projects diff --git a/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties b/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties index f6b5c80b02..12a58c6075 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language_ja.properties @@ -67,6 +67,8 @@ approved.releases.total.number.of.releases=承認済リリース数 / 総リリ a.project.with.the.same.name.and.version.already.exists=同名・同バージョンのプロジェクトが既に存在します。 a.release.with.the.same.name.and.version.already.exists=同名・同バージョンのリリースが既に存在します。 assessment.date=評価日 +assessment.summary=Assessment Summary +assessment.summary.information.is.not.present.in.CLI.file=Assessment Summary information is not present in CLI file assessor.contact.person=評価者の連絡先 assessor.department=評価部門 assigned.external.component.ids=外部コンポーネントID割当て @@ -820,6 +822,7 @@ more.info=詳細情報 more.than.1000.documents.affected.the.merge.operation.might.time.out.in.consequence.only.some.of=1000以上のドキュメントが影響を受けています。結果として、一部のマージ操作がタイムアウトする可能性があります。 multiple.approved.cli.are.found.in.the.release=承認された複数のCLIがリリースに見つかりました. multiple.attachments.with.same.name.or.content.cannot.be.present.in.attachment.list=同じ名前または内容の複数の添付ファイルが添付ファイル リストに存在することはできません。 +multiple.cli.are.found.in.the.release=Multiple CLI are found in release multiple.isr.are.found.in.the.release=Multiple ISR (Initial Scan Reports) are found in release my.components=マイコンポーネント my.projects=マイプロジェクト diff --git a/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties b/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties index 248ccb0a81..3040a91335 100644 --- a/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties +++ b/frontend/sw360-portlet/src/main/resources/content/Language_vi.properties @@ -68,6 +68,8 @@ approved.releases.total.number.of.releases=bản phát hành đã được chấ a.project.with.the.same.name.and.version.already.exists=Một dự án có cùng tên và phiên bản đã tồn tại. a.release.with.the.same.name.and.version.already.exists=Một bản phát hành có cùng tên và phiên bản đã tồn tại. assessment.date=Ngày thẩm định +assessment.summary=Assessment Summary +assessment.summary.information.is.not.present.in.CLI.file=Assessment Summary information is not present in CLI file assessor.contact.person=Người liên hệ thẩm định assessor.department=Bộ phận thẩm định assigned.external.component.ids=Những id thành phần bên ngoài được chỉ định @@ -824,6 +826,7 @@ more.info=Thêm thông tin more.than.1000.documents.affected.the.merge.operation.might.time.out.in.consequence.only.some.of=Hơn 1000 tài liệu bị ảnh hưởng. Hoạt động hợp nhất có thể hết thời gian. Do đó, chỉ có một số multiple.approved.cli.are.found.in.the.release=Multiple approved CLI are found in release multiple.attachments.with.same.name.or.content.cannot.be.present.in.attachment.list=Nhiều tệp đính kèm có cùng tên hoặc nội dung không thể có trong danh sách tệp đính kèm. +multiple.cli.are.found.in.the.release=Multiple CLI are found in release multiple.isr.are.found.in.the.release=Multiple ISR (Initial Scan Reports) are found in release my.components=Thành phần của tôi my.projects=Dự án của tôi diff --git a/libraries/lib-datahandler/src/main/thrift/licenseinfo.thrift b/libraries/lib-datahandler/src/main/thrift/licenseinfo.thrift index e195ed84f7..1594b612bf 100644 --- a/libraries/lib-datahandler/src/main/thrift/licenseinfo.thrift +++ b/libraries/lib-datahandler/src/main/thrift/licenseinfo.thrift @@ -63,7 +63,8 @@ struct LicenseInfo { 23: optional string componentName, 24: optional set concludedLicenseIds, 25: optional i32 totalObligations, - 26: optional map> copyrightsWithFilesHash + 26: optional map> copyrightsWithFilesHash, + 27: optional map assessmentSummary } struct LicenseInfoParsingResult {
    :