/*
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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 com.google.api.server.spi.auth;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.http.LowLevelHttpResponse;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.api.server.spi.auth.GoogleAuth.TokenInfo;
import com.google.api.server.spi.response.ServiceUnavailableException;
import com.google.common.collect.ImmutableList;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.springframework.mock.web.MockHttpServletRequest;

import java.io.IOException;

/**
 * Test for GoogleAuthUtil.
 */
@RunWith(JUnit4.class)
public class GoogleAuthTest {
  private static final String ACCESS_TOKEN = GoogleAuth.OAUTH2_TOKEN_PREFIXES[0] + "abc123";
  private static final String CLIENT_ID1 = "clientId1";
  private static final String CLIENT_ID2 = "clientId2";
  private static final ImmutableList<String> CLIENT_ID_LIST = ImmutableList.of(CLIENT_ID1);
  private static final String AUDIENCE1 = "audience1";
  private static final String AUDIENCE2 = "audience2";
  private static final ImmutableList<String> AUDIENCE_LIST = ImmutableList.of(AUDIENCE1);

  private static final String SAMPLE_CONTENT_WITHOUT_EMAIL = "{\n"
      + " \"issued_to\": \"123.apps.googleusercontent.com\",\n"
      + " \"audience\": \"123.apps.googleusercontent.com\",\n"
      + " \"scope\": \"https://www.googleapis.com/auth/xapi.zoo\",\n" + " \"expires_in\": 3581,\n"
      + " \"access_type\": \"online\"\n" + "}";

  private static final String SAMPLE_CONTENT_WITH_EMAIL = "{\n"
      + " \"issued_to\": \"123.apps.googleusercontent.com\",\n"
      + " \"audience\": \"123.apps.googleusercontent.com\",\n" + " \"user_id\": \"1234567\",\n"
      + " \"scope\": \"https://www.googleapis.com/auth/userinfo.email"
      + " https://www.googleapis.com/auth/xapi.zoo https://www.googleapis.com/auth/plus.me\",\n"
      + " \"expires_in\": 3574,\n" + " \"email\": \"[email protected]\",\n"
      + " \"verified_email\": true,\n" + " \"access_type\": \"online\"\n" + "}";

  @Test
  public void testIsJwt() {
    assertFalse(GoogleAuth.isJwt("ya29.abcdef"));
    assertFalse(GoogleAuth.isJwt("abcdef.abcdef"));
    assertFalse(GoogleAuth.isJwt("abc.abcdef.abcdef"));
    assertFalse(GoogleAuth.isJwt("abcdef.abc.abcdef"));
    assertFalse(GoogleAuth.isJwt("abcdef.abcdef.abc"));
    assertFalse(GoogleAuth.isJwt("abcdef.abcdef.abcdef.abcdef"));
    assertFalse(GoogleAuth.isJwt("abcdef.abcd*ef.abcdef"));
    assertTrue(GoogleAuth.isJwt("abcdef.abcdef.abcdef"));
  }

  @Test
  public void testIsOAuth2Token() {
    for (String prefix : GoogleAuth.OAUTH2_TOKEN_PREFIXES) {
      assertTrue(GoogleAuth.isOAuth2Token(prefix + "abc"));
      assertFalse(GoogleAuth.isOAuth2Token("x" + prefix + "abc"));
    }
  }

  @Test
  public void testGetAuthToken_fromHeaders() {
    for (String scheme : GoogleAuth.ALLOWED_AUTH_SCHEMES) {
      MockHttpServletRequest request = new MockHttpServletRequest();

      request.addHeader(GoogleAuth.AUTHORIZATION_HEADER, scheme + " some-value");
      assertEquals("some-value", GoogleAuth.getAuthToken(request));
    }
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.addHeader(GoogleAuth.AUTHORIZATION_HEADER, "noSuchAuthScheme some-value");
    assertNull(GoogleAuth.getAuthToken(request));
  }

  @Test
  public void testGetAuthToken_queryParameter() {
    for (String parameterName : GoogleAuth.BEARER_TOKEN_PARAMETER_NAMES) {
      testGetAuthTokenFromQueryParameter(parameterName);
    }
  }

  private void testGetAuthTokenFromQueryParameter(String parameterName) {
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setParameter(parameterName, ACCESS_TOKEN);
    assertEquals(ACCESS_TOKEN, GoogleAuth.getAuthToken(request));
  }

  @Test
  public void testCheckClientId() {
    // Empty allowed list or client Id.
    assertFalse(GoogleAuth.checkClientId(CLIENT_ID1, ImmutableList.<String>of(), true));
    assertFalse(GoogleAuth.checkClientId(CLIENT_ID1, null, true));
    assertFalse(GoogleAuth.checkClientId("", CLIENT_ID_LIST, true));
    assertFalse(GoogleAuth.checkClientId(null, CLIENT_ID_LIST, true));

    // ["*"]
    assertTrue(
        GoogleAuth.checkClientId(CLIENT_ID1, GoogleAuth.SKIP_CLIENT_ID_CHECK_LIST, true));
    assertFalse(
        GoogleAuth.checkClientId(CLIENT_ID1, GoogleAuth.SKIP_CLIENT_ID_CHECK_LIST, false));

    // In or not in whitelist.
    assertTrue(GoogleAuth.checkClientId(CLIENT_ID1, CLIENT_ID_LIST, true));
    assertFalse(GoogleAuth.checkClientId(CLIENT_ID2, CLIENT_ID_LIST, true));
  }

  @Test
  public void testCheckAudience() {
    // Empty allowed list or audience.
    assertFalse(GoogleAuth.checkAudience(AUDIENCE1, ImmutableList.<String>of(), CLIENT_ID1));
    assertFalse(GoogleAuth.checkAudience(AUDIENCE1, null, CLIENT_ID1));
    assertFalse(GoogleAuth.checkAudience("", AUDIENCE_LIST, CLIENT_ID1));
    assertFalse(GoogleAuth.checkAudience(null, AUDIENCE_LIST, CLIENT_ID1));

    // In or not in whitelist.
    assertTrue(GoogleAuth.checkAudience(AUDIENCE1, AUDIENCE_LIST, CLIENT_ID1));
    assertFalse(GoogleAuth.checkAudience(AUDIENCE2, AUDIENCE_LIST, CLIENT_ID1));

    // Equals to client id.
    assertTrue(GoogleAuth.checkAudience(CLIENT_ID1, AUDIENCE_LIST, CLIENT_ID1));
  }

  @Test
  public void testParseTokenInfo_withEmail() throws Exception {
    HttpRequest request = constructHttpRequest(SAMPLE_CONTENT_WITH_EMAIL);
    TokenInfo info = GoogleAuth.parseTokenInfo(request);
    assertEquals("123.apps.googleusercontent.com", info.clientId);
    assertEquals("https://www.googleapis.com/auth/userinfo.email"
        + " https://www.googleapis.com/auth/xapi.zoo" + " https://www.googleapis.com/auth/plus.me",
        info.scopes);
    assertEquals("1234567", info.userId);
    assertEquals("[email protected]", info.email);
  }

  @Test
  public void testParseTokenInfo_withoutEmail() throws Exception {
    HttpRequest request = constructHttpRequest(SAMPLE_CONTENT_WITHOUT_EMAIL);
    assertNull(GoogleAuth.parseTokenInfo(request));
  }

  @Test
  public void testParseTokenInfo_with400() throws Exception {
    HttpRequest request = constructHttpRequest("{\"error_description\": \"Invalid Value\"}", 400);
    assertNull(GoogleAuth.parseTokenInfo(request));
  }

  @Test(expected = ServiceUnavailableException.class)
  public void testParseTokenInfo_with500() throws Exception {
    HttpRequest request = constructHttpRequest("{\"error_description\": \"Backend Error\"}", 500);
    GoogleAuth.parseTokenInfo(request);
  }

  private HttpRequest constructHttpRequest(final String content) throws IOException {
    return constructHttpRequest(content, 200);
  }

  private HttpRequest constructHttpRequest(final String content, final int statusCode) throws IOException {
    HttpTransport transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
        return new MockLowLevelHttpRequest() {
          @Override
          public LowLevelHttpResponse execute() throws IOException {
            MockLowLevelHttpResponse result = new MockLowLevelHttpResponse();
            result.setContentType("application/json");
            result.setContent(content);
            result.setStatusCode(statusCode);
            return result;
          }
        };
      }
    };
    HttpRequest httpRequest = transport.createRequestFactory().buildGetRequest(new GenericUrl("https://google.com")).setParser(new JsonObjectParser(new JacksonFactory()));
    GoogleAuth.configureErrorHandling(httpRequest);
    return httpRequest;
  }
}