Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions cli/src/main/java/com/okta/cli/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@
*/
package com.okta.cli;

import com.okta.cli.common.model.OktaProfile;
import com.okta.cli.common.service.DefaultProfileConfigurationService;
import com.okta.cli.common.service.ProfileConfigurationService;
import com.okta.cli.console.ConsoleOutput;
import com.okta.cli.console.DefaultPrompter;
import com.okta.cli.console.DisabledPrompter;
import com.okta.cli.console.Prompter;

import java.io.File;
import java.io.IOException;
import java.util.Optional;

public class Environment {

Expand All @@ -38,6 +43,10 @@ public class Environment {

private boolean consoleColors = true;

private String profile = null;

private boolean profileActivated = false;

public boolean isInteractive() {
return interactive;
}
Expand Down Expand Up @@ -68,6 +77,81 @@ public File getOktaPropsFile() {
return oktaPropsFile;
}

/**
* Gets the currently selected profile name.
* Returns the profile set via --profile flag, or the active profile from config,
* or "default" if no profile is configured.
*
* @return the profile name
*/
public String getProfile() {
if (profile != null) {
return profile;
}

// Check environment variable
String envProfile = System.getenv("OKTA_CLI_PROFILE");
if (envProfile != null && !envProfile.isEmpty()) {
return envProfile;
}

// Get active profile from config file
try {
ProfileConfigurationService profileService = new DefaultProfileConfigurationService();
return profileService.getActiveProfileName(oktaPropsFile);
} catch (IOException e) {
return ProfileConfigurationService.DEFAULT_PROFILE_NAME;
}
}

/**
* Sets the profile to use for this session.
* This overrides the active profile from configuration.
*
* @param profile the profile name
* @return this Environment for chaining
*/
public Environment setProfile(String profile) {
this.profile = profile;
this.profileActivated = false; // Reset so profile will be activated on next SDK call
return this;
}

/**
* Activates the current profile by setting system properties for the Okta SDK.
* This method is idempotent - calling it multiple times has no additional effect.
*
* @throws IllegalStateException if the profile does not exist or cannot be loaded
*/
public void activateProfile() {
if (profileActivated) {
return;
}

ProfileConfigurationService profileService = new DefaultProfileConfigurationService();

try {
// Migrate legacy format if needed
if (((DefaultProfileConfigurationService) profileService).isLegacyFormat(oktaPropsFile)) {
((DefaultProfileConfigurationService) profileService).migrateFromLegacyFormat(oktaPropsFile);
}

String profileName = getProfile();
Optional<OktaProfile> profileOpt = profileService.getProfile(oktaPropsFile, profileName);

if (profileOpt.isPresent()) {
profileService.activateProfileForSdk(profileOpt.get());
profileActivated = true;
}
// If profile doesn't exist, let the SDK handle the error (might be using env vars)
} catch (IOException e) {
// If we can't read the config, let the SDK try its default behavior
if (verbose) {
System.err.println("Warning: Could not load profile configuration: " + e.getMessage());
}
}
}

public boolean isDemo() {
return Boolean.parseBoolean(System.getenv("OKTA_CLI_DEMO"));
}
Expand Down
7 changes: 7 additions & 0 deletions cli/src/main/java/com/okta/cli/OktaCli.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.okta.cli.commands.Register;
import com.okta.cli.commands.Start;
import com.okta.cli.commands.apps.Apps;
import com.okta.cli.commands.profiles.Profiles;
import com.okta.commons.lang.ApplicationInfo;
import com.okta.sdk.resource.ResourceException;
import picocli.AutoComplete;
Expand All @@ -43,6 +44,7 @@
Register.class,
Login.class,
Apps.class,
Profiles.class,
Start.class,
Logs.class,
DumpCommand.class,
Expand Down Expand Up @@ -166,6 +168,11 @@ public void setSystemProperties(List<String> props) {
}
}

@Option(names = {"-p", "--profile"}, description = "Use a specific Okta profile. Overrides the active profile from configuration.")
public void setProfile(String profile) {
environment.setProfile(profile);
}

public Environment getEnvironment() {
return environment;
}
Expand Down
3 changes: 3 additions & 0 deletions cli/src/main/java/com/okta/cli/commands/BaseCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public BaseCommand(OktaCli.StandardOptions standardOptions) {

@Override
public Integer call() throws Exception {
// Activate the selected profile before running the command
// This sets system properties so the Okta SDK uses the correct credentials
getEnvironment().activateProfile();
return runCommand();
}

Expand Down
75 changes: 60 additions & 15 deletions cli/src/main/java/com/okta/cli/commands/Login.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,87 @@
*/
package com.okta.cli.commands;

import com.okta.cli.common.service.DefaultSdkConfigurationService;
import com.okta.cli.common.service.SdkConfigurationService;
import com.okta.cli.common.model.OktaProfile;
import com.okta.cli.common.service.DefaultProfileConfigurationService;
import com.okta.cli.common.service.ProfileConfigurationService;
import com.okta.cli.console.ConsoleOutput;
import com.okta.commons.configcheck.ConfigurationValidator;
import com.okta.commons.lang.Strings;
import com.okta.sdk.impl.config.ClientConfiguration;
import picocli.CommandLine;

import java.io.File;
import java.util.Optional;

@CommandLine.Command(name = "login",
description = "Authorizes the Okta CLI tool")
public class Login extends BaseCommand {

@CommandLine.Option(names = {"--profile-name"}, description = "Name for this profile (e.g., 'acme-corp', 'dev-tenant')")
private String profileName;

@Override
public int runCommand() throws Exception {

// check if okta client config exists?
SdkConfigurationService sdkConfigurationService = new DefaultSdkConfigurationService();
ClientConfiguration clientConfiguration = sdkConfigurationService.loadUnvalidatedConfiguration();
String orgUrl = clientConfiguration.getBaseUrl();
ProfileConfigurationService profileService = new DefaultProfileConfigurationService();
File configFile = getEnvironment().getOktaPropsFile();

// Migrate legacy format if needed
if (((DefaultProfileConfigurationService) profileService).isLegacyFormat(configFile)) {
((DefaultProfileConfigurationService) profileService).migrateFromLegacyFormat(configFile);
}

// Determine profile name: --profile-name flag > --profile flag > prompt
String targetProfile = profileName;
if (targetProfile == null) {
String envProfile = getEnvironment().getProfile();
if (!ProfileConfigurationService.DEFAULT_PROFILE_NAME.equals(envProfile)) {
targetProfile = envProfile;
}
}

try (ConsoleOutput out = getConsoleOutput()) {
// prompt user to overwrite config file
if (Strings.hasText(orgUrl)
&& !configQuestions().isOverwriteExistingConfig(orgUrl, getEnvironment().getOktaPropsFile().getAbsolutePath())) {
return 0;
// Prompt for profile name if not provided
if (targetProfile == null) {
targetProfile = getPrompter().promptUntilIfEmpty(null, "Profile name", ProfileConfigurationService.DEFAULT_PROFILE_NAME);
}

// prompt for Base URL
orgUrl = getPrompter().promptUntilValue("Okta Org URL");
// Check if profile already exists
Optional<OktaProfile> existingProfile = profileService.getProfile(configFile, targetProfile);
if (existingProfile.isPresent()) {
out.writeLine("Profile '" + targetProfile + "' already exists with org: " + existingProfile.get().getOrgUrl());
if (!getPrompter().promptYesNo("Overwrite this profile?")) {
return 0;
}
}

// Prompt for Okta Org URL
String orgUrl = getPrompter().promptUntilValue("Okta Org URL");
ConfigurationValidator.assertOrgUrl(orgUrl);

// Prompt for API token
out.writeLine("Enter your Okta API token, for more information see: https://bit.ly/get-okta-api-token");
String apiToken = getPrompter().promptUntilValue(null, "Okta API token");
ConfigurationValidator.assertApiToken(apiToken);

sdkConfigurationService.writeOktaYaml(orgUrl, apiToken, getEnvironment().getOktaPropsFile());
// Determine if this should be the active profile
boolean setAsActive = true;
String currentActive = profileService.getActiveProfileName(configFile);
if (!currentActive.equals(targetProfile) && profileService.profileExists(configFile, currentActive)) {
setAsActive = getPrompter().promptYesNo("Set '" + targetProfile + "' as the active profile?", true);
}

// Save the profile
OktaProfile newProfile = new OktaProfile(targetProfile, orgUrl, apiToken);
profileService.saveProfile(configFile, newProfile, setAsActive);

out.writeLine("");
out.bold("Profile '" + targetProfile + "' saved successfully!");
out.writeLine("");
out.writeLine("Org URL: " + orgUrl);
if (setAsActive) {
out.writeLine("This profile is now active.");
} else {
out.writeLine("Use 'okta --profile " + targetProfile + " <command>' or 'okta profiles use " + targetProfile + "' to switch.");
}
}

return 0;
Expand Down
39 changes: 39 additions & 0 deletions cli/src/main/java/com/okta/cli/commands/profiles/Profiles.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2020-Present Okta, 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.okta.cli.commands.profiles;

import picocli.CommandLine;

@CommandLine.Command(name = "profiles",
description = "Manage Okta CLI profiles for multiple organizations",
subcommands = {
ProfilesList.class,
ProfilesUse.class,
ProfilesShow.class,
ProfilesDelete.class,
CommandLine.HelpCommand.class
})
public class Profiles implements Runnable {

@CommandLine.Spec
private CommandLine.Model.CommandSpec spec;

@Override
public void run() {
// Default action: list profiles
spec.commandLine().execute("list");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2020-Present Okta, 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.okta.cli.commands.profiles;

import com.okta.cli.commands.BaseCommand;
import com.okta.cli.common.model.OktaProfile;
import com.okta.cli.common.service.DefaultProfileConfigurationService;
import com.okta.cli.common.service.ProfileConfigurationService;
import com.okta.cli.console.ConsoleOutput;
import picocli.CommandLine;

import java.io.File;
import java.util.Optional;

@CommandLine.Command(name = "delete",
description = "Delete a profile")
public class ProfilesDelete extends BaseCommand {

@CommandLine.Parameters(index = "0", description = "The profile name to delete")
private String profileName;

@CommandLine.Option(names = {"-f", "--force"}, description = "Skip confirmation prompt")
private boolean force;

@Override
public int runCommand() throws Exception {
ProfileConfigurationService profileService = new DefaultProfileConfigurationService();
File configFile = getEnvironment().getOktaPropsFile();

try (ConsoleOutput out = getConsoleOutput()) {
// Check if profile exists
Optional<OktaProfile> profile = profileService.getProfile(configFile, profileName);
if (profile.isEmpty()) {
out.writeError("Profile '" + profileName + "' not found.");
return 1;
}

// Check if it's the active profile
String activeProfile = profileService.getActiveProfileName(configFile);
if (profileName.equals(activeProfile)) {
out.writeError("Cannot delete the active profile '" + profileName + "'.");
out.writeLine("Switch to another profile first using 'okta profiles use <name>'.");
return 1;
}

// Confirm deletion
if (!force) {
out.writeLine("Profile: " + profileName);
out.writeLine("Org URL: " + profile.get().getOrgUrl());
if (!getPrompter().promptYesNo("Delete this profile?", false)) {
out.writeLine("Cancelled.");
return 0;
}
}

// Delete the profile
boolean deleted = profileService.deleteProfile(configFile, profileName);
if (deleted) {
out.writeLine("Profile '" + profileName + "' deleted.");
} else {
out.writeError("Failed to delete profile '" + profileName + "'.");
return 1;
}
}

return 0;
}
}
Loading