]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java
Make CMS Login refresh more robust.
[lgpl/argeo-commons.git] / org.argeo.enterprise / src / org / argeo / osgi / useradmin / UserAdminConf.java
1 package org.argeo.osgi.useradmin;
2
3 import java.io.IOException;
4 import java.net.InetAddress;
5 import java.net.URI;
6 import java.net.URISyntaxException;
7 import java.net.UnknownHostException;
8 import java.util.Dictionary;
9 import java.util.Hashtable;
10 import java.util.List;
11 import java.util.Map;
12
13 import javax.naming.Context;
14 import javax.naming.NamingException;
15 import javax.naming.ldap.LdapName;
16
17 import org.argeo.naming.DnsBrowser;
18 import org.argeo.naming.NamingUtils;
19
20 /** Properties used to configure user admins. */
21 public enum UserAdminConf {
22 /** Base DN (cannot be configured externally) */
23 baseDn("dc=example,dc=com"),
24
25 /** URI of the underlying resource (cannot be configured externally) */
26 uri("ldap://localhost:10389"),
27
28 /** User objectClass */
29 userObjectClass("inetOrgPerson"),
30
31 /** Relative base DN for users */
32 userBase("ou=People"),
33
34 /** Groups objectClass */
35 groupObjectClass("groupOfNames"),
36
37 /** Relative base DN for users */
38 groupBase("ou=Groups"),
39
40 /** Read-only source */
41 readOnly(null),
42
43 /** Disabled source */
44 disabled(null),
45
46 /** Authentication realm */
47 realm(null);
48
49 public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
50
51 public final static String SCHEME_LDAP = "ldap";
52 public final static String SCHEME_FILE = "file";
53 public final static String SCHEME_OS = "os";
54 public final static String SCHEME_IPA = "ipa";
55
56 /** The default value. */
57 private Object def;
58
59 UserAdminConf(Object def) {
60 this.def = def;
61 }
62
63 public Object getDefault() {
64 return def;
65 }
66
67 /**
68 * For use as Java property.
69 *
70 * @deprecated use {@link #name()} instead
71 */
72 @Deprecated
73 public String property() {
74 return name();
75 }
76
77 public String getValue(Dictionary<String, ?> properties) {
78 Object res = getRawValue(properties);
79 if (res == null)
80 return null;
81 return res.toString();
82 }
83
84 @SuppressWarnings("unchecked")
85 public <T> T getRawValue(Dictionary<String, ?> properties) {
86 Object res = properties.get(name());
87 if (res == null)
88 res = getDefault();
89 return (T) res;
90 }
91
92 /** @deprecated use {@link #valueOf(String)} instead */
93 @Deprecated
94 public static UserAdminConf local(String property) {
95 return UserAdminConf.valueOf(property);
96 }
97
98 /** Hides host and credentials. */
99 public static URI propertiesAsUri(Dictionary<String, ?> properties) {
100 StringBuilder query = new StringBuilder();
101
102 boolean first = true;
103 // for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
104 // String key = keys.nextElement();
105 // // TODO clarify which keys are relevant (list only the enum?)
106 // if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn")
107 // && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name())
108 // && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS)
109 // && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) {
110 // if (first)
111 // first = false;
112 // else
113 // query.append('&');
114 // query.append(valueOf(key).name());
115 // query.append('=').append(properties.get(key).toString());
116 // }
117 // }
118
119 keys: for (UserAdminConf key : UserAdminConf.values()) {
120 if (key.equals(baseDn) || key.equals(uri))
121 continue keys;
122 Object value = properties.get(key.name());
123 if (value == null)
124 continue keys;
125 if (first)
126 first = false;
127 else
128 query.append('&');
129 query.append(key.name());
130 query.append('=').append(value.toString());
131
132 }
133
134 Object bDnObj = properties.get(baseDn.name());
135 String bDn = bDnObj != null ? bDnObj.toString() : null;
136 try {
137 return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null,
138 null);
139 } catch (URISyntaxException e) {
140 throw new UserDirectoryException("Cannot create URI from properties", e);
141 }
142 }
143
144 public static Dictionary<String, Object> uriAsProperties(String uriStr) {
145 try {
146 Hashtable<String, Object> res = new Hashtable<String, Object>();
147 URI u = new URI(uriStr);
148 String scheme = u.getScheme();
149 if (scheme != null && scheme.equals(SCHEME_IPA)) {
150 u = convertIpaConfig(u);
151 scheme = u.getScheme();
152 }
153 String path = u.getPath();
154 // base DN
155 String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
156 if (bDn.equals("") && SCHEME_OS.equals(scheme)) {
157 bDn = getBaseDnFromHostname();
158 }
159
160 if (bDn.endsWith(".ldif"))
161 bDn = bDn.substring(0, bDn.length() - ".ldif".length());
162
163 // Normalize base DN as LDAP name
164 bDn = new LdapName(bDn).toString();
165
166 String principal = null;
167 String credentials = null;
168 if (scheme != null)
169 if (scheme.equals(SCHEME_LDAP) || scheme.equals("ldaps")) {
170 // TODO additional checks
171 if (u.getUserInfo() != null) {
172 String[] userInfo = u.getUserInfo().split(":");
173 principal = userInfo.length > 0 ? userInfo[0] : null;
174 credentials = userInfo.length > 1 ? userInfo[1] : null;
175 }
176 } else if (scheme.equals(SCHEME_FILE)) {
177 } else if (scheme.equals(SCHEME_IPA)) {
178 } else if (scheme.equals(SCHEME_OS)) {
179 } else
180 throw new UserDirectoryException("Unsupported scheme " + scheme);
181 Map<String, List<String>> query = NamingUtils.queryToMap(u);
182 for (String key : query.keySet()) {
183 UserAdminConf ldapProp = UserAdminConf.valueOf(key);
184 List<String> values = query.get(key);
185 if (values.size() == 1) {
186 res.put(ldapProp.name(), values.get(0));
187 } else {
188 throw new UserDirectoryException("Only single values are supported");
189 }
190 }
191 res.put(baseDn.name(), bDn);
192 if (SCHEME_OS.equals(scheme))
193 res.put(readOnly.name(), "true");
194 if (principal != null)
195 res.put(Context.SECURITY_PRINCIPAL, principal);
196 if (credentials != null)
197 res.put(Context.SECURITY_CREDENTIALS, credentials);
198 if (scheme != null) {// relative URIs are dealt with externally
199 if (SCHEME_OS.equals(scheme)) {
200 res.put(uri.name(), SCHEME_OS + ":///");
201 } else {
202 URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(),
203 scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null);
204 res.put(uri.name(), bareUri.toString());
205 }
206 }
207 return res;
208 } catch (Exception e) {
209 throw new UserDirectoryException("Cannot convert " + uri + " to properties", e);
210 }
211 }
212
213 private static URI convertIpaConfig(URI uri) {
214 String path = uri.getPath();
215 String kerberosRealm;
216 if (path == null || path.length() <= 1) {
217 kerberosRealm = kerberosDomainFromDns();
218 } else {
219 kerberosRealm = path.substring(1);
220 }
221
222 if (kerberosRealm == null)
223 throw new UserDirectoryException("No Kerberos domain available for " + uri);
224 try (DnsBrowser dnsBrowser = new DnsBrowser()) {
225 String ldapHostsStr = uri.getHost();
226 if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
227 List<String> ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase());
228 if (ldapHosts == null || ldapHosts.size() == 0) {
229 throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri);
230 } else {
231 ldapHostsStr = ldapHosts.get(0);
232 }
233 }
234 URI convertedUri = new URI(
235 SCHEME_LDAP + "://" + ldapHostsStr + "/" + IpaUtils.domainToUserDirectoryConfigPath(kerberosRealm));
236 return convertedUri;
237 } catch (NamingException | IOException | URISyntaxException e) {
238 throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
239 }
240 }
241
242 private static String kerberosDomainFromDns() {
243 String kerberosDomain;
244 try (DnsBrowser dnsBrowser = new DnsBrowser()) {
245 InetAddress localhost = InetAddress.getLocalHost();
246 String hostname = localhost.getHostName();
247 String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
248 kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
249 return kerberosDomain;
250 } catch (Exception e) {
251 throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e);
252 }
253
254 }
255
256 private static String getBaseDnFromHostname() {
257 String hostname;
258 try {
259 hostname = InetAddress.getLocalHost().getHostName();
260 } catch (UnknownHostException e) {
261 hostname = "localhost.localdomain";
262 }
263 int dotIdx = hostname.indexOf('.');
264 if (dotIdx >= 0) {
265 String domain = hostname.substring(dotIdx + 1, hostname.length());
266 String bDn = ("." + domain).replaceAll("\\.", ",dc=");
267 bDn = bDn.substring(1, bDn.length());
268 return bDn;
269 } else {
270 return "dc=" + hostname;
271 }
272 }
273
274 /**
275 * Hash the base DN in order to have a deterministic string to be used as a cn
276 * for the underlying user directory.
277 */
278 public static String baseDnHash(Dictionary<String, Object> properties) {
279 String bDn = (String) properties.get(baseDn.name());
280 if (bDn == null)
281 throw new UserDirectoryException("No baseDn in " + properties);
282 return DigestUtils.sha1str(bDn);
283 }
284 }