001/* 002 * Copyright 2022 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.odfsync.pegase.ws; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.nio.charset.StandardCharsets; 021import java.time.Instant; 022import java.util.ArrayList; 023import java.util.Base64; 024import java.util.Base64.Decoder; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.avalon.framework.activity.Initializable; 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.commons.io.IOUtils; 034import org.apache.http.HttpEntity; 035import org.apache.http.NameValuePair; 036import org.apache.http.StatusLine; 037import org.apache.http.client.HttpResponseException; 038import org.apache.http.client.config.RequestConfig; 039import org.apache.http.client.entity.UrlEncodedFormEntity; 040import org.apache.http.client.methods.CloseableHttpResponse; 041import org.apache.http.client.methods.HttpPost; 042import org.apache.http.client.methods.HttpUriRequest; 043import org.apache.http.impl.client.CloseableHttpClient; 044import org.apache.http.impl.client.HttpClientBuilder; 045import org.apache.http.message.BasicNameValuePair; 046 047import org.ametys.core.util.HttpUtils; 048import org.ametys.core.util.JSONUtils; 049import org.ametys.runtime.config.Config; 050import org.ametys.runtime.plugin.component.AbstractLogEnabled; 051 052/** 053 * Manager to request Pégase token when needed. 054 */ 055public class PegaseTokenManager extends AbstractLogEnabled implements Component, Serviceable, Initializable 056{ 057 /** Role */ 058 public static final String ROLE = PegaseTokenManager.class.getName(); 059 060 /** The JSON utils */ 061 protected JSONUtils _jsonUtils; 062 063 /* Token */ 064 private String _token; 065 private Instant _expirationDate; 066 067 /* Configuration */ 068 private boolean _isActive; 069 private String _username; 070 private String _password; 071 private String _authUrl; 072 073 public void service(ServiceManager manager) throws ServiceException 074 { 075 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 076 } 077 078 public void initialize() throws Exception 079 { 080 _isActive = Config.getInstance().getValue("pegase.activate", true, false); 081 if (_isActive) 082 { 083 /* Authentication configuration */ 084 _authUrl = HttpUtils.sanitize(Config.getInstance().getValue("pegase.auth.url")); 085 _username = Config.getInstance().getValue("pegase.api.username"); 086 _password = Config.getInstance().getValue("pegase.api.password"); 087 } 088 } 089 /** 090 * Get the token to log to Pégase API 091 * @throws IOException if an error occurs 092 * @return a valid token 093 */ 094 public synchronized String getToken() throws IOException 095 { 096 if (!_isActive) 097 { 098 throw new UnsupportedOperationException("Pégase is not active in the configuration, you cannot request a token."); 099 } 100 101 if (_expirationDate == null || Instant.now().isAfter(_expirationDate)) 102 { 103 CloseableHttpClient httpClient = _getHttpClient(); 104 105 List<NameValuePair> urlParams = new ArrayList<>(); 106 urlParams.add(new BasicNameValuePair("username", _username)); 107 urlParams.add(new BasicNameValuePair("password", _password)); 108 urlParams.add(new BasicNameValuePair("token", "true")); 109 110 // Prepare a request object 111 HttpPost postRequest = new HttpPost(_authUrl); 112 113 // HTTP parameters 114 postRequest.setEntity(new UrlEncodedFormEntity(urlParams, "UTF-8")); 115 postRequest.setHeader("Content-Type", "application/x-www-form-urlencoded"); 116 117 // Execute the request 118 _token = _executeHttpRequest(httpClient, postRequest); 119 _expirationDate = _extractExpirationDate(); 120 } 121 122 return _token; 123 } 124 125 /** 126 * Extract the expiration date from the token. 127 * @return the expiration dat of the current token 128 */ 129 protected Instant _extractExpirationDate() 130 { 131 String[] splitToken = _token.split("\\."); 132 133 if (splitToken.length < 2) 134 { 135 getLogger().error("Invalid token format, cannot get the expiration date. The token will be reset at each API call."); 136 return null; 137 } 138 139 String playload = splitToken[1]; 140 Decoder decoder = Base64.getUrlDecoder(); 141 byte[] decodedToken = decoder.decode(playload); 142 143 String decodedTokenString = new String(decodedToken, StandardCharsets.UTF_8); 144 Map<String, Object> map = _jsonUtils.convertJsonToMap(decodedTokenString); 145 Long expirationDateInSeconds = Long.parseLong(map.get("exp").toString()); 146 147 return Instant.ofEpochSecond(expirationDateInSeconds); 148 } 149 150 private CloseableHttpClient _getHttpClient() 151 { 152 RequestConfig requestConfig = RequestConfig.custom().build(); 153 return HttpClientBuilder.create() 154 .setDefaultRequestConfig(requestConfig) 155 .useSystemProperties() 156 .build(); 157 } 158 159 private String _executeHttpRequest(CloseableHttpClient httpClient, HttpUriRequest httpRequest) throws IOException 160 { 161 httpRequest.setHeader("accept", "application/json"); 162 163 // Execute the request 164 try (CloseableHttpResponse httpResponse = httpClient.execute(httpRequest)) 165 { 166 StatusLine statusLine = httpResponse.getStatusLine(); 167 if (statusLine.getStatusCode() / 100 != 2) 168 { 169 throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); 170 } 171 172 HttpEntity entity = httpResponse.getEntity(); 173 if (entity == null) 174 { 175 throw new IOException("The response entity is empty."); 176 } 177 178 try (InputStream is = entity.getContent()) 179 { 180 return IOUtils.toString(is, StandardCharsets.UTF_8); 181 } 182 } 183 } 184}