|
| 1 | +/** |
| 2 | + * Licensed to The Apereo Foundation under one or more contributor license |
| 3 | + * agreements. See the NOTICE file distributed with this work for additional |
| 4 | + * information regarding copyright ownership. |
| 5 | + * |
| 6 | + * |
| 7 | + * The Apereo Foundation licenses this file to you under the Educational |
| 8 | + * Community License, Version 2.0 (the "License"); you may not use this file |
| 9 | + * except in compliance with the License. You may obtain a copy of the License |
| 10 | + * at: |
| 11 | + * |
| 12 | + * http://opensource.org/licenses/ecl2.txt |
| 13 | + * |
| 14 | + * Unless required by applicable law or agreed to in writing, software |
| 15 | + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 16 | + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 17 | + * License for the specific language governing permissions and limitations under |
| 18 | + * the License. |
| 19 | + * |
| 20 | + */ |
| 21 | + |
| 22 | +package org.opencastproject.kernel.security; |
| 23 | + |
| 24 | +import org.apache.commons.io.IOUtils; |
| 25 | +import org.slf4j.Logger; |
| 26 | +import org.slf4j.LoggerFactory; |
| 27 | +import org.springframework.security.core.userdetails.UserDetailsService; |
| 28 | +import org.springframework.security.crypto.codec.Hex; |
| 29 | +import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; |
| 30 | + |
| 31 | +import java.io.File; |
| 32 | +import java.io.FileInputStream; |
| 33 | +import java.io.IOException; |
| 34 | +import java.net.InetAddress; |
| 35 | +import java.net.UnknownHostException; |
| 36 | +import java.nio.charset.StandardCharsets; |
| 37 | +import java.security.MessageDigest; |
| 38 | +import java.security.NoSuchAlgorithmException; |
| 39 | +import java.util.Arrays; |
| 40 | +import java.util.Objects; |
| 41 | + |
| 42 | +/** |
| 43 | + * This implements a zero-configuration version Spring Security's token based remember-me service. While the key can |
| 44 | + * still be augmented by configuration, it is generally generated based on seldom changing but unique system |
| 45 | + * properties like hostname, IP address, file system information and Linux kernel. |
| 46 | + */ |
| 47 | +public class SystemTokenBasedRememberMeService extends TokenBasedRememberMeServices { |
| 48 | + private Logger logger = LoggerFactory.getLogger(SystemTokenBasedRememberMeService.class); |
| 49 | + private String key; |
| 50 | + |
| 51 | + @Deprecated |
| 52 | + public SystemTokenBasedRememberMeService() { |
| 53 | + super(); |
| 54 | + setKey(null); |
| 55 | + } |
| 56 | + |
| 57 | + public SystemTokenBasedRememberMeService(String key, UserDetailsService userDetailsService) { |
| 58 | + super(key, userDetailsService); |
| 59 | + setKey(key); |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Set a new key to be used when generating remember-me tokens. |
| 64 | + * |
| 65 | + * Note that the key passed to this method will be augmented by seldom changing but generally unique system |
| 66 | + * properties like hostname, IP address, file system information and Linux kernel. Hence, even setting no custom |
| 67 | + * key should be save. |
| 68 | + */ |
| 69 | + @Override |
| 70 | + public void setKey(String key) { |
| 71 | + // Start with a user key if provided |
| 72 | + StringBuilder keyBuilder = new StringBuilder(Objects.toString(key, "")); |
| 73 | + |
| 74 | + // This will give us the hostname and IP address as something which should be unique per system. |
| 75 | + // For example: lk.elan-ev.de/10.10.10.31 |
| 76 | + try { |
| 77 | + keyBuilder.append(InetAddress.getLocalHost()); |
| 78 | + } catch (UnknownHostException e) { |
| 79 | + // silently ignore this |
| 80 | + } |
| 81 | + |
| 82 | + // Gather additional system properties as key |
| 83 | + // This requires a proc-fs which should generally be available under Linux. |
| 84 | + // But even without, we have fallbacks above and below. |
| 85 | + for (String procFile: Arrays.asList("/proc/version", "/proc/partitions")) { |
| 86 | + try (FileInputStream fileInputStream = new FileInputStream(new File(procFile))) { |
| 87 | + keyBuilder.append(IOUtils.toString(fileInputStream, StandardCharsets.UTF_8)); |
| 88 | + } catch (IOException e) { |
| 89 | + // ignore this |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + // If we still have no proper key, just generate a random one. |
| 94 | + // This will work just fine with the single drawback that restarting Opencast invalidates all remember-me tokens. |
| 95 | + // But it should be a sufficiently good fallback. |
| 96 | + key = keyBuilder.toString(); |
| 97 | + if (key.isEmpty()) { |
| 98 | + logger.warn("Could not generate semi-persistent remember-me key. Will generate a non-persistent random one."); |
| 99 | + key = Double.toString(Math.random()); |
| 100 | + } |
| 101 | + logger.debug("Remember me key before hashing: {}", key); |
| 102 | + |
| 103 | + // Use a SHA-512 hash as key to have a more sane key. |
| 104 | + try { |
| 105 | + MessageDigest digest = MessageDigest.getInstance("SHA-512"); |
| 106 | + key = new String(Hex.encode(digest.digest(key.getBytes()))); |
| 107 | + } catch (NoSuchAlgorithmException e) { |
| 108 | + logger.warn("No SHA-512 algorithm available!"); |
| 109 | + } |
| 110 | + logger.debug("Calculated remember me key: {}", key); |
| 111 | + this.key = key; |
| 112 | + super.setKey(key); |
| 113 | + } |
| 114 | + |
| 115 | + @Override |
| 116 | + public String getKey() { |
| 117 | + return this.key; |
| 118 | + } |
| 119 | + |
| 120 | + /** |
| 121 | + * Calculates the digital signature to be put in the cookie. Default value is |
| 122 | + * SHA-512 ("username:tokenExpiryTime:password:key") |
| 123 | + */ |
| 124 | + @Override |
| 125 | + protected String makeTokenSignature(long tokenExpiryTime, String username, String password) { |
| 126 | + String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey(); |
| 127 | + MessageDigest digest; |
| 128 | + try { |
| 129 | + digest = MessageDigest.getInstance("SHA-512"); |
| 130 | + } catch (NoSuchAlgorithmException e) { |
| 131 | + throw new IllegalStateException("No SHA-512 algorithm available!"); |
| 132 | + } |
| 133 | + |
| 134 | + return new String(Hex.encode(digest.digest(data.getBytes()))); |
| 135 | + } |
| 136 | +} |
0 commit comments