2017-01-27 00:08:52 +00:00
package okta
import (
2018-01-08 18:31:38 +00:00
"context"
2017-01-27 00:08:52 +00:00
"fmt"
2020-02-03 17:51:10 +00:00
"github.com/hashicorp/go-cleanhttp"
"net/http"
2017-01-27 00:08:52 +00:00
"net/url"
2019-02-01 16:23:40 +00:00
"time"
2020-02-03 17:51:10 +00:00
oktaold "github.com/chrismalek/oktasdk-go/okta"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/framework"
2019-07-01 20:30:30 +00:00
"github.com/hashicorp/vault/sdk/helper/tokenutil"
2019-04-13 07:44:06 +00:00
"github.com/hashicorp/vault/sdk/logical"
2020-02-03 17:51:10 +00:00
oktanew "github.com/okta/okta-sdk-golang/okta"
2017-01-27 00:08:52 +00:00
)
2017-09-15 04:27:45 +00:00
const (
defaultBaseURL = "okta.com"
previewBaseURL = "oktapreview.com"
)
2017-01-27 00:08:52 +00:00
func pathConfig ( b * backend ) * framework . Path {
2019-07-01 20:30:30 +00:00
p := & framework . Path {
2017-01-27 00:08:52 +00:00
Pattern : ` config ` ,
Fields : map [ string ] * framework . FieldSchema {
"organization" : & framework . FieldSchema {
Type : framework . TypeString ,
2019-06-27 18:52:52 +00:00
Description : "Use org_name instead." ,
2019-02-14 17:42:44 +00:00
Deprecated : true ,
2017-08-31 02:37:21 +00:00
} ,
"org_name" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Name of the organization to be used in the Okta API." ,
2019-06-21 15:08:08 +00:00
DisplayAttrs : & framework . DisplayAttributes {
Name : "Organization Name" ,
} ,
2017-01-27 00:08:52 +00:00
} ,
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
2019-06-27 18:52:52 +00:00
Description : "Use api_token instead." ,
2019-02-14 17:42:44 +00:00
Deprecated : true ,
2017-08-31 02:37:21 +00:00
} ,
"api_token" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Okta API key." ,
2019-06-21 15:08:08 +00:00
DisplayAttrs : & framework . DisplayAttributes {
Name : "API Token" ,
} ,
2017-01-27 00:08:52 +00:00
} ,
"base_url" : & framework . FieldSchema {
2017-09-15 04:27:45 +00:00
Type : framework . TypeString ,
2018-03-20 18:54:10 +00:00
Description : ` The base domain to use for the Okta API. When not specified in the configuration, "okta.com" is used. ` ,
2019-06-21 15:08:08 +00:00
DisplayAttrs : & framework . DisplayAttributes {
Name : "Base URL" ,
} ,
2017-08-31 02:37:21 +00:00
} ,
"production" : & framework . FieldSchema {
Type : framework . TypeBool ,
2019-06-27 18:52:52 +00:00
Description : ` Use base_url instead. ` ,
2019-02-14 17:42:44 +00:00
Deprecated : true ,
2017-01-27 00:08:52 +00:00
} ,
2019-02-01 16:23:40 +00:00
"ttl" : & framework . FieldSchema {
Type : framework . TypeDurationSecond ,
2019-07-01 20:30:30 +00:00
Description : tokenutil . DeprecationText ( "token_ttl" ) ,
Deprecated : true ,
2019-02-01 16:23:40 +00:00
} ,
"max_ttl" : & framework . FieldSchema {
Type : framework . TypeDurationSecond ,
2019-07-01 20:30:30 +00:00
Description : tokenutil . DeprecationText ( "token_max_ttl" ) ,
Deprecated : true ,
2019-02-01 16:23:40 +00:00
} ,
2018-02-09 22:03:49 +00:00
"bypass_okta_mfa" : & framework . FieldSchema {
Type : framework . TypeBool ,
Description : ` When set true, requests by Okta for a MFA check will be bypassed. This also disallows certain status checks on the account, such as whether the password is expired. ` ,
2019-06-21 15:08:08 +00:00
DisplayAttrs : & framework . DisplayAttributes {
Name : "Bypass Okta MFA" ,
} ,
2018-02-09 22:03:49 +00:00
} ,
2017-01-27 00:08:52 +00:00
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ReadOperation : b . pathConfigRead ,
logical . CreateOperation : b . pathConfigWrite ,
logical . UpdateOperation : b . pathConfigWrite ,
} ,
ExistenceCheck : b . pathConfigExistenceCheck ,
HelpSynopsis : pathConfigHelp ,
2019-10-17 23:19:14 +00:00
DisplayAttrs : & framework . DisplayAttributes {
Action : "Configure" ,
} ,
2017-01-27 00:08:52 +00:00
}
2019-07-01 20:30:30 +00:00
tokenutil . AddTokenFields ( p . Fields )
p . Fields [ "token_policies" ] . Description += ". This will apply to all tokens generated by this auth method, in addition to any configured for specific users/groups."
return p
2017-01-27 00:08:52 +00:00
}
// Config returns the configuration for this backend.
2018-01-19 06:44:44 +00:00
func ( b * backend ) Config ( ctx context . Context , s logical . Storage ) ( * ConfigEntry , error ) {
entry , err := s . Get ( ctx , "config" )
2017-01-27 00:08:52 +00:00
if err != nil {
return nil , err
}
if entry == nil {
return nil , nil
}
var result ConfigEntry
if entry != nil {
if err := entry . DecodeJSON ( & result ) ; err != nil {
return nil , err
}
}
2019-07-01 20:30:30 +00:00
if result . TokenTTL == 0 && result . TTL > 0 {
result . TokenTTL = result . TTL
}
if result . TokenMaxTTL == 0 && result . MaxTTL > 0 {
result . TokenMaxTTL = result . MaxTTL
}
2017-01-27 00:08:52 +00:00
return & result , nil
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathConfigRead ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2018-01-19 06:44:44 +00:00
cfg , err := b . Config ( ctx , req . Storage )
2017-01-27 00:08:52 +00:00
if err != nil {
return nil , err
}
if cfg == nil {
return nil , nil
}
2019-07-01 20:30:30 +00:00
data := map [ string ] interface { } {
"organization" : cfg . Org ,
"org_name" : cfg . Org ,
"bypass_okta_mfa" : cfg . BypassOktaMFA ,
2017-01-27 00:08:52 +00:00
}
2019-07-01 20:30:30 +00:00
cfg . PopulateTokenData ( data )
2017-08-31 02:37:21 +00:00
if cfg . BaseURL != "" {
2019-07-01 20:30:30 +00:00
data [ "base_url" ] = cfg . BaseURL
2017-08-31 02:37:21 +00:00
}
2017-09-15 04:27:45 +00:00
if cfg . Production != nil {
2019-07-01 20:30:30 +00:00
data [ "production" ] = * cfg . Production
}
if cfg . TTL > 0 {
data [ "ttl" ] = int64 ( cfg . TTL . Seconds ( ) )
}
if cfg . MaxTTL > 0 {
data [ "max_ttl" ] = int64 ( cfg . MaxTTL . Seconds ( ) )
}
resp := & logical . Response {
Data : data ,
2017-09-15 04:27:45 +00:00
}
2017-01-27 00:08:52 +00:00
2018-02-09 22:03:49 +00:00
if cfg . BypassOktaMFA {
resp . AddWarning ( "Okta MFA bypass is configured. In addition to ignoring Okta MFA requests, certain other account statuses will not be seen, such as PASSWORD_EXPIRED. Authentication will succeed in these cases." )
}
2017-01-27 00:08:52 +00:00
return resp , nil
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathConfigWrite ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2018-01-19 06:44:44 +00:00
cfg , err := b . Config ( ctx , req . Storage )
2017-01-27 00:08:52 +00:00
if err != nil {
return nil , err
}
// Due to the existence check, entry will only be nil if it's a create
// operation, so just create a new one
if cfg == nil {
2017-08-24 22:18:05 +00:00
cfg = & ConfigEntry { }
}
2017-08-31 02:37:21 +00:00
org , ok := d . GetOk ( "org_name" )
2017-08-24 22:18:05 +00:00
if ok {
cfg . Org = org . ( string )
2017-08-31 02:37:21 +00:00
}
if cfg . Org == "" {
org , ok = d . GetOk ( "organization" )
if ok {
cfg . Org = org . ( string )
}
}
if cfg . Org == "" && req . Operation == logical . CreateOperation {
return logical . ErrorResponse ( "org_name is missing" ) , nil
2017-01-27 00:08:52 +00:00
}
2017-08-31 02:37:21 +00:00
token , ok := d . GetOk ( "api_token" )
2017-01-27 00:08:52 +00:00
if ok {
cfg . Token = token . ( string )
2018-09-28 15:28:06 +00:00
} else if token , ok = d . GetOk ( "token" ) ; ok {
cfg . Token = token . ( string )
2017-08-31 02:37:21 +00:00
}
2017-01-27 00:08:52 +00:00
2017-09-15 04:27:45 +00:00
baseURLRaw , ok := d . GetOk ( "base_url" )
2017-01-27 00:08:52 +00:00
if ok {
2017-09-15 04:27:45 +00:00
baseURL := baseURLRaw . ( string )
_ , err = url . Parse ( fmt . Sprintf ( "https://%s,%s" , cfg . Org , baseURL ) )
if err != nil {
return logical . ErrorResponse ( fmt . Sprintf ( "Error parsing given base_url: %s" , err ) ) , nil
2017-01-27 00:08:52 +00:00
}
2017-09-15 04:27:45 +00:00
cfg . BaseURL = baseURL
2017-01-27 00:08:52 +00:00
}
2017-09-15 04:27:45 +00:00
// We only care about the production flag when base_url is not set. It is
// for compatibility reasons.
if cfg . BaseURL == "" {
productionRaw , ok := d . GetOk ( "production" )
if ok {
production := productionRaw . ( bool )
cfg . Production = & production
}
} else {
// clear out old production flag if base_url is set
cfg . Production = nil
}
2017-08-31 02:37:21 +00:00
2018-02-09 22:03:49 +00:00
bypass , ok := d . GetOk ( "bypass_okta_mfa" )
if ok {
cfg . BypassOktaMFA = bypass . ( bool )
}
2019-07-01 20:30:30 +00:00
if err := cfg . ParseTokenFields ( req , d ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
2019-02-01 16:23:40 +00:00
}
2019-07-01 20:30:30 +00:00
// Handle upgrade cases
{
2019-07-02 13:52:05 +00:00
if err := tokenutil . UpgradeValue ( d , "ttl" , "token_ttl" , & cfg . TTL , & cfg . TokenTTL ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , nil
2019-07-01 20:30:30 +00:00
}
2019-07-02 13:52:05 +00:00
if err := tokenutil . UpgradeValue ( d , "max_ttl" , "token_max_ttl" , & cfg . MaxTTL , & cfg . TokenMaxTTL ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , nil
2019-07-01 20:30:30 +00:00
}
2017-08-24 22:18:05 +00:00
}
2017-07-05 13:42:37 +00:00
2017-01-27 00:08:52 +00:00
jsonCfg , err := logical . StorageEntryJSON ( "config" , cfg )
if err != nil {
return nil , err
}
2018-01-19 06:44:44 +00:00
if err := req . Storage . Put ( ctx , jsonCfg ) ; err != nil {
2017-01-27 00:08:52 +00:00
return nil , err
}
2018-02-09 22:03:49 +00:00
var resp * logical . Response
if cfg . BypassOktaMFA {
resp = new ( logical . Response )
resp . AddWarning ( "Okta MFA bypass is configured. In addition to ignoring Okta MFA requests, certain other account statuses will not be seen, such as PASSWORD_EXPIRED. Authentication will succeed in these cases." )
}
return resp , nil
2017-01-27 00:08:52 +00:00
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathConfigExistenceCheck ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( bool , error ) {
2018-01-19 06:44:44 +00:00
cfg , err := b . Config ( ctx , req . Storage )
2017-01-27 00:08:52 +00:00
if err != nil {
return false , err
}
return cfg != nil , nil
}
2020-02-03 17:51:10 +00:00
type oktaShim interface {
Client ( ) * oktanew . Client
NewRequest ( method string , url string , body interface { } ) ( * http . Request , error )
Do ( req * http . Request , v interface { } ) ( interface { } , error )
}
type oktaShimNew struct {
client * oktanew . Client
}
func ( new * oktaShimNew ) Client ( ) * oktanew . Client {
return new . client
}
func ( new * oktaShimNew ) NewRequest ( method string , url string , body interface { } ) ( * http . Request , error ) {
return new . client . GetRequestExecutor ( ) . NewRequest ( method , url , body )
}
func ( new * oktaShimNew ) Do ( req * http . Request , v interface { } ) ( interface { } , error ) {
return new . client . GetRequestExecutor ( ) . Do ( req , v )
}
type oktaShimOld struct {
client * oktaold . Client
}
func ( new * oktaShimOld ) Client ( ) * oktanew . Client {
return nil
}
func ( new * oktaShimOld ) NewRequest ( method string , url string , body interface { } ) ( * http . Request , error ) {
return new . client . NewRequest ( method , url , body )
}
func ( new * oktaShimOld ) Do ( req * http . Request , v interface { } ) ( interface { } , error ) {
return new . client . Do ( req , v )
}
2017-01-27 00:08:52 +00:00
// OktaClient creates a basic okta client connection
2020-02-03 17:51:10 +00:00
func ( c * ConfigEntry ) OktaClient ( ) ( oktaShim , error ) {
2017-09-15 04:27:45 +00:00
baseURL := defaultBaseURL
2017-08-31 02:37:21 +00:00
if c . Production != nil {
2017-09-15 04:27:45 +00:00
if ! * c . Production {
baseURL = previewBaseURL
}
2017-01-27 00:08:52 +00:00
}
2017-08-31 02:37:21 +00:00
if c . BaseURL != "" {
2017-09-15 04:27:45 +00:00
baseURL = c . BaseURL
2017-01-27 00:08:52 +00:00
}
2017-09-15 04:27:45 +00:00
2020-02-03 17:51:10 +00:00
if c . Token != "" {
client , err := oktanew . NewClient ( context . Background ( ) ,
oktanew . WithOrgUrl ( "https://" + c . Org + "." + baseURL ) ,
oktanew . WithToken ( c . Token ) )
if err != nil {
return nil , err
}
return & oktaShimNew { client } , nil
}
client , err := oktaold . NewClientWithDomain ( cleanhttp . DefaultClient ( ) , c . Org , baseURL , "" )
if err != nil {
return nil , err
}
return & oktaShimOld { client } , nil
2017-01-27 00:08:52 +00:00
}
// ConfigEntry for Okta
type ConfigEntry struct {
2019-07-01 20:30:30 +00:00
tokenutil . TokenParams
2019-02-01 16:23:40 +00:00
Org string ` json:"organization" `
Token string ` json:"token" `
BaseURL string ` json:"base_url" `
Production * bool ` json:"is_production,omitempty" `
TTL time . Duration ` json:"ttl" `
MaxTTL time . Duration ` json:"max_ttl" `
BypassOktaMFA bool ` json:"bypass_okta_mfa" `
2017-01-27 00:08:52 +00:00
}
const pathConfigHelp = `
This endpoint allows you to configure the Okta and its
configuration options .
The Okta organization are the characters at the front of the URL for Okta .
Example https : //ORG.okta.com
`