content: /**
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.aurora.scheduler.http.api.security;
  
  import javax.inject.Inject;
  import javax.security.auth.kerberos.KerberosPrincipal;
  
  import com.google.common.base.Splitter;
  import com.google.common.collect.ImmutableList;
  
  import org.apache.shiro.authc.AuthenticationException;
  import org.apache.shiro.authc.AuthenticationInfo;
  import org.apache.shiro.authc.AuthenticationToken;
  import org.apache.shiro.authc.SimpleAuthenticationInfo;
  import org.apache.shiro.realm.Realm;
  import org.apache.shiro.subject.SimplePrincipalCollection;
  import org.ietf.jgss.GSSContext;
  import org.ietf.jgss.GSSCredential;
  import org.ietf.jgss.GSSException;
  import org.ietf.jgss.GSSManager;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * Authentication-only realm for Kerberos V5.
   */
  class Kerberos5Realm implements Realm {
    private static final Splitter AT_SPLITTER = Splitter.on("@");
  
    private final GSSManager gssManager;
    private final GSSCredential serverCredential;
  
    @Inject
    Kerberos5Realm(GSSManager gssManager, GSSCredential serverCredential) {
      this.gssManager = requireNonNull(gssManager);
      this.serverCredential = requireNonNull(serverCredential);
    }
  
    @Override
    public String getName() {
      return getClass().getName();
    }
  
    @Override
    public boolean supports(AuthenticationToken token) {
      return token instanceof AuthorizeHeaderToken;
    }
  
    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
        throws AuthenticationException {
  
      byte[] tokenFromInitiator = ((AuthorizeHeaderToken) token).getAuthorizeHeaderValue();
      GSSContext context;
      try {
        context = gssManager.createContext(serverCredential);
        context.acceptSecContext(tokenFromInitiator, 0, tokenFromInitiator.length);
      } catch (GSSException e) {
        throw new AuthenticationException(e);
      }
  
      // Technically the GSS-API requires us to continue sending data back and forth in a loop
      // until the context is established, but we can short-circuit here since we know we're using
      // Kerberos V5 directly or Kerberos V5-backed SPNEGO. This is important because it means we
      // don't need to keep state between requests.
      // From http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/single-signon.html
      // "In the case of the Kerberos V5 mechanism, there is no more than one round trip of
      // tokens during context establishment."
      if (context.isEstablished()) {
        try {
          KerberosPrincipal kerberosPrincipal =
              new KerberosPrincipal(context.getSrcName().toString());
          return new SimpleAuthenticationInfo(
              new SimplePrincipalCollection(
                  ImmutableList.of(
                      // We assume there's a single Kerberos realm in use here. Most Authorizer
                      // implementations care about the "simple" username instead of the full
                      // principal.
                      AT_SPLITTER.splitToList(kerberosPrincipal.getName()).get(0),
                      kerberosPrincipal),
                  getName()),
              null /* There are no credentials that can be cached. */);
        } catch (GSSException | IndexOutOfBoundsException e) {
          throw new AuthenticationException(e);
        }
      } else {
        throw new AuthenticationException("GSSContext was not established with a single message.");
      }
    }
  }
  