View Javadoc
1   /*
2    * Copyright 2013–2021 Michael Osipov
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.michaelo.tomcat.realm.mapper;
17  
18  import java.util.Arrays;
19  import java.util.Locale;
20  
21  import javax.naming.Name;
22  import javax.naming.NameParser;
23  import javax.naming.NamingException;
24  import javax.naming.directory.DirContext;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.ietf.jgss.GSSException;
28  import org.ietf.jgss.GSSName;
29  import org.ietf.jgss.Oid;
30  
31  /**
32   * A mapper for the AD attribute {@code sAMAccountName} and the realm. This mapper splits the GSS
33   * name in the primary and realm component. The instance component is completely ignored. The
34   * primary component is assigned to the {@code sAMAccountName} and the realm is transformed to a
35   * search base according to <a href="https://tools.ietf.org/html/rfc2247">RFC 2247</a>. Moreover,
36   * this implementation mimics
37   * <a href="https://docs.microsoft.com/de-de/windows/win32/api/ntdsapi/nf-ntdsapi-dscracknamesw">
38   * {@code DsCrackNames}</a> with {@code formatOffered} set to {@code DS_USER_PRINCIPAL_NAME} and
39   * {@code formatDesired} set to {@code DS_FQDN_1779_NAME}. Verified against <a href=
40   * "https://github.com/samba-team/samba/blob/7ed24924d2917556a03c51eadcb65b3e3c1e8af6/source4/dsdb/samdb/cracknames.c#L1260">
41   * Samba's implementation</a> of {@code DsCrackNames}.
42   * <p>
43   * <strong>Note:</strong> This mapper requires to operate from the {@code RootDSE} of a domain
44   * controller or better yet, a GC. No root DN normalization (stripping DC components) happens here
45   * (yet).
46   */
47  public class SamAccountNameRfc2247Mapper extends SamAccountNameMapper {
48  
49  	protected final static Oid KRB5_NT_PRINCIPAL;
50  
51  	static {
52  		try {
53  			KRB5_NT_PRINCIPAL = new Oid("1.2.840.113554.1.2.2.1");
54  		} catch (GSSException e) {
55  			throw new IllegalStateException("Failed to create OID for KRB5_NT_PRINCIPAL");
56  		}
57  	}
58  
59  	private static final Oid[] SUPPORTED_STRING_NAME_TYPES = new Oid[] { KRB5_NT_PRINCIPAL };
60  
61  	@Override
62  	public Oid[] getSupportedStringNameTypes() {
63  		return Arrays.copyOf(SUPPORTED_STRING_NAME_TYPES, SUPPORTED_STRING_NAME_TYPES.length);
64  	}
65  
66  	@Override
67  	public boolean supportsGssName(GSSName gssName) {
68  		try {
69  			return gssName.getStringNameType().containedIn(SUPPORTED_STRING_NAME_TYPES);
70  		} catch (GSSException e) {
71  			// Can this ever happen?
72  			return false;
73  		}
74  	}
75  
76  	public synchronized MappedValues map(DirContext context, GSSName gssName)
77  			throws NamingException {
78  		if (!supportsGssName(gssName))
79  			throw new IllegalArgumentException("GSS name '" + gssName + "' is not supported");
80  
81  		String[] upnComponents = StringUtils.split(gssName.toString(), '@');
82  		String samAccountName = upnComponents[0];
83  		String realm = upnComponents[1];
84  		String searchBase = StringUtils.EMPTY;
85  
86  		String[] realmComponents = StringUtils.split(realm, '.');
87  		NameParser parser = context.getNameParser(StringUtils.EMPTY);
88  		Name searchBaseName = parser.parse(StringUtils.EMPTY);
89  
90  		for (int i = realmComponents.length - 1; i >= 0; i--) {
91  			searchBaseName.add("DC=" + realmComponents[i].toLowerCase(Locale.ROOT));
92  		}
93  
94  		searchBase = searchBaseName.toString();
95  
96  		return new SamAccountNameMappedValues(searchBase, samAccountName);
97  
98  	}
99  }