All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.spinnaker.gate.services.PermissionService Maven / Gradle / Ivy

/*
 * Copyright 2016 Google, Inc.
 * Copyright 2023 Apple, Inc.
 *
 * 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.netflix.spinnaker.gate.services;

import com.netflix.spinnaker.fiat.model.UserPermission;
import com.netflix.spinnaker.fiat.model.resources.Role;
import com.netflix.spinnaker.fiat.model.resources.ServiceAccount;
import com.netflix.spinnaker.fiat.shared.FiatPermissionEvaluator;
import com.netflix.spinnaker.fiat.shared.FiatService;
import com.netflix.spinnaker.fiat.shared.FiatStatus;
import com.netflix.spinnaker.gate.retrofit.UpstreamBadRequest;
import com.netflix.spinnaker.gate.security.SpinnakerUser;
import com.netflix.spinnaker.gate.services.internal.ExtendedFiatService;
import com.netflix.spinnaker.kork.core.RetrySupport;
import com.netflix.spinnaker.kork.exceptions.SpinnakerException;
import com.netflix.spinnaker.kork.exceptions.SystemException;
import com.netflix.spinnaker.security.AuthenticatedRequest;
import com.netflix.spinnaker.security.User;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import retrofit.RetrofitError;

@Log4j2
@Component
@RequiredArgsConstructor
public class PermissionService {
  private final FiatService fiatService;
  private final ExtendedFiatService extendedFiatService;
  private final ServiceAccountFilterConfigProps serviceAccountFilterConfigProps;
  private final FiatPermissionEvaluator permissionEvaluator;
  private final FiatStatus fiatStatus;

  @Setter(
      onParam_ = {@Qualifier("fiatLoginService")},
      onMethod_ = {@Autowired(required = false)})
  private FiatService fiatLoginService;

  public boolean isEnabled() {
    return fiatStatus.isEnabled();
  }

  private FiatService getFiatServiceForLogin() {
    return fiatLoginService != null ? fiatLoginService : fiatService;
  }

  public void login(final String userId) {
    if (fiatStatus.isEnabled()) {
      try {
        AuthenticatedRequest.allowAnonymous(
            () -> {
              // TODO(jvz): FiatService::loginUser should have only one parameter as Retrofit no
              // longer requires this body parameter
              getFiatServiceForLogin().loginUser(userId, "");
              permissionEvaluator.invalidatePermission(userId);
              return null;
            });
      } catch (RetrofitError e) {
        throw UpstreamBadRequest.classifyError(e);
      }
    }
  }

  public void loginWithRoles(final String userId, final Collection roles) {
    if (fiatStatus.isEnabled()) {
      try {
        AuthenticatedRequest.allowAnonymous(
            () -> {
              getFiatServiceForLogin().loginWithRoles(userId, roles);
              permissionEvaluator.invalidatePermission(userId);
              return null;
            });
      } catch (RetrofitError e) {
        throw UpstreamBadRequest.classifyError(e);
      }
    }
  }

  public void logout(String userId) {
    if (fiatStatus.isEnabled()) {
      try {
        getFiatServiceForLogin().logoutUser(userId);
        permissionEvaluator.invalidatePermission(userId);
      } catch (RetrofitError e) {
        throw UpstreamBadRequest.classifyError(e);
      }
    }
  }

  public void sync() {
    if (fiatStatus.isEnabled()) {
      try {
        getFiatServiceForLogin().sync(List.of());
      } catch (RetrofitError e) {
        throw UpstreamBadRequest.classifyError(e);
      }
    }
  }

  public Set getRoles(String userId) {
    if (!fiatStatus.isEnabled()) {
      return Set.of();
    }
    try {
      var permission = permissionEvaluator.getPermission(userId);
      var roles = permission != null ? permission.getRoles() : null;
      return roles != null ? roles : Set.of();
    } catch (RetrofitError e) {
      throw UpstreamBadRequest.classifyError(e);
    }
  }

  List lookupServiceAccounts(String userId) {
    try {
      return extendedFiatService.getUserServiceAccounts(userId);
    } catch (RetrofitError re) {
      var response = re.getResponse();
      if (response != null && response.getStatus() == HttpStatus.NOT_FOUND.value()) {
        return List.of();
      }
      boolean shouldRetry =
          response == null || HttpStatus.valueOf(response.getStatus()).is5xxServerError();
      throw new SystemException("getUserServiceAccounts failed", re).setRetryable(shouldRetry);
    }
  }

  public List getServiceAccountsForApplication(
      @SpinnakerUser final User user, @Nonnull final String application) {
    var matchAuthorizations = serviceAccountFilterConfigProps.getMatchAuthorizations();
    boolean requiresFiltering =
        fiatStatus.isEnabled()
            && serviceAccountFilterConfigProps.isEnabled()
            && user != null
            && StringUtils.hasLength(application)
            && !CollectionUtils.isEmpty(matchAuthorizations);
    if (!requiresFiltering) {
      return getServiceAccounts(user);
    }

    List filteredServiceAccounts;
    RetrySupport retry = new RetrySupport();
    try {
      var serviceAccounts =
          retry.retry(
              () -> lookupServiceAccounts(user.getUsername()), 3, Duration.ofMillis(50), false);
      filteredServiceAccounts =
          serviceAccounts.stream()
              .filter(
                  permission ->
                      permission.getApplications().stream()
                          .anyMatch(
                              app ->
                                  application.equalsIgnoreCase(app.getName())
                                      && !Collections.disjoint(
                                          matchAuthorizations, app.getAuthorizations())))
              .map(UserPermission.View::getName)
              .collect(Collectors.toList());
    } catch (SpinnakerException se) {
      log.error(
          "failed to lookup user {} service accounts for application {}, falling back to all user service accounts",
          user,
          application,
          se);
      return getServiceAccounts(user);
    }

    // if there are no service accounts for the requested application, fall back to the full list of
    // service accounts for the user to avoid a chicken and egg problem of trying to enable security
    // for the first time on an application
    return !filteredServiceAccounts.isEmpty() ? filteredServiceAccounts : getServiceAccounts(user);
  }

  public List getServiceAccounts(@SpinnakerUser User user) {
    if (user == null) {
      log.debug("getServiceAccounts: Spinnaker user is null.");
      return List.of();
    }

    if (!fiatStatus.isEnabled()) {
      log.debug("getServiceAccounts: Fiat disabled.");
      return List.of();
    }

    try {
      var permission = permissionEvaluator.getPermission(user.getUsername());
      if (permission == null) {
        return List.of();
      }
      return permission.getServiceAccounts().stream()
          .map(ServiceAccount.View::getName)
          .collect(Collectors.toList());
    } catch (RetrofitError re) {
      throw UpstreamBadRequest.classifyError(re);
    }
  }

  public boolean isAdmin(String userId) {
    var permission = permissionEvaluator.getPermission(userId);
    return permission != null && permission.isAdmin();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy