View Javadoc
1   /*
2    * Copyright 2013–2026 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 net.sf.michaelo.tomcat.StringUtils;
27  
28  import org.ietf.jgss.GSSException;
29  import org.ietf.jgss.GSSName;
30  import org.ietf.jgss.Oid;
31  
32  /**
33   * A mapper for the AD attribute {@code sAMAccountName} and the realm. This mapper splits the GSS name in the primary
34   * and realm component. The instance component is completely ignored. The primary component is assigned to the
35   * {@code sAMAccountName} and the realm is transformed to a search base according to <a
36   * href="https://www.rfc-editor.org/info/rfc2247/">RFC 2247</a>. Moreover, this implementation mimics <a
37   * href="https://learn.microsoft.com/de-de/windows/win32/api/ntdsapi/nf-ntdsapi-dscracknamesw">{@code DsCrackNames}</a>
38   * with {@code formatOffered} set to {@code DS_USER_PRINCIPAL_NAME} and {@code formatDesired} set to
39   * {@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   *
43   * <p><strong>Note:</strong> This mapper requires to operate from the {@code RootDSE} of a domain controller or better
44   * yet, a GC. No root DN normalization (stripping DC components) happens here (yet).
45   */
46  public class SamAccountNameRfc2247Mapper extends SamAccountNameMapper {
47  
48  	protected static final Oid KRB5_NT_PRINCIPAL;
49  
50  	static {
51  		try {
52  			KRB5_NT_PRINCIPAL = new Oid("1.2.840.113554.1.2.2.1");
53  		} catch (GSSException e) {
54  			throw new IllegalStateException("Failed to create OID for KRB5_NT_PRINCIPAL");
55  		}
56  	}
57  
58  	private static final Oid[] SUPPORTED_STRING_NAME_TYPES = new Oid[] {KRB5_NT_PRINCIPAL};
59  
60  	@Override
61  	public Oid[] getSupportedStringNameTypes() {
62  		return Arrays.copyOf(SUPPORTED_STRING_NAME_TYPES, SUPPORTED_STRING_NAME_TYPES.length);
63  	}
64  
65  	@Override
66  	public boolean supportsGssName(GSSName gssName) {
67  		try {
68  			return gssName.getStringNameType().containedIn(SUPPORTED_STRING_NAME_TYPES);
69  		} catch (GSSException e) {
70  			// Can this ever happen?
71  			return false;
72  		}
73  	}
74  
75  	public synchronized MappedValues map(DirContext context, GSSName gssName) throws NamingException {
76  		if (!supportsGssName(gssName))
77  			throw new IllegalArgumentException("GSS name '" + gssName + "' is not supported");
78  
79  		String[] upnComponents = StringUtils.split(gssName.toString(), "@");
80  		String samAccountName = upnComponents[0];
81  		String realm = upnComponents[1];
82  		String searchBase = StringUtils.EMPTY;
83  
84  		String[] realmComponents = StringUtils.split(realm, ".");
85  		NameParser parser = context.getNameParser(StringUtils.EMPTY);
86  		Name searchBaseName = parser.parse(StringUtils.EMPTY);
87  
88  		for (int i = realmComponents.length - 1; i >= 0; i--) {
89  			searchBaseName.add("DC=" + realmComponents[i].toLowerCase(Locale.ROOT));
90  		}
91  
92  		searchBase = searchBaseName.toString();
93  
94  		return new SamAccountNameMappedValues(searchBase, samAccountName);
95  	}
96  }