Skip to content
Draft
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
19 changes: 16 additions & 3 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@ import (
"io/ioutil"
"os"
"strconv"
"time"

_ "github.com/lib/pq"
)

// ClientSecretExpiryNotificationFunc is a callback function type for client secret expiry notifications.
// It's called when a client secret is about to expire within the configured threshold.
// Parameters:
// - providerName: Name of the OAuth provider or "MSAAD" for MSAAD configuration
// - daysUntilExpiry: Number of days until the secret expires
// - expiryDate: The actual expiry date of the secret
type ClientSecretExpiryNotificationFunc func(providerName string, daysUntilExpiry int, expiryDate time.Time)

/*

Full populated config:
Expand Down Expand Up @@ -57,13 +66,17 @@ Full populated config:
"RedirectURL": "https://mysite.example.com/auth/oauth/finish",
"ClientID": "your client UUID here",
"Scope": "openid email offline_access",
"ClientSecret": "your secret here"
"ClientSecret": "your secret here",
"ClientSecretExpiryDate": "2024-12-31T23:59:59Z"
}
}
},
"SecretExpiryNotificationDays": 14,
"SecretExpiryCheckIntervalHours": 1
},
"MSAAD": {
"ClientID": "your client UUID",
"ClientSecret": "your secret"
"ClientSecret": "your secret",
"ClientSecretExpiryDate": "2024-12-31T23:59:59Z"
},
"SessionDB": {
"MaxActiveSessions": 0,
Expand Down
72 changes: 62 additions & 10 deletions msaad.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,20 @@ import (

// ConfigMSAAD is the JSON definition for the Microsoft Azure Active Directory synchronization settings
type ConfigMSAAD struct {
Verbose bool // If true, then emit verbose logging
DryRun bool // If true, don't actually take any action, just log the intended actions
TenantID string // Your tenant UUID (ie ID of your AAD instance)
ClientID string // Your client UUID (ie ID of your application)
ClientSecret string // Secrets used for authenticating Azure AD requests
MergeIntervalSeconds int // If non-zero, then overrides the merge interval
DefaultRoles []string // Roles that are activated by default if a user has any one of the AAD roles
RoleToGroup map[string]string // Map from principleName of AAD role, to Authaus group.
AllowArchiveUser bool // If true, then archive users who no longer have the relevant roles in the AAD
PassthroughClientIDs []string // Client IDs of trusted IMQS apps utilising app-to-app passthrough auth
Verbose bool // If true, then emit verbose logging
DryRun bool // If true, don't actually take any action, just log the intended actions
TenantID string // Your tenant UUID (ie ID of your AAD instance)
ClientID string // Your client UUID (ie ID of your application)
ClientSecret string // Secrets used for authenticating Azure AD requests
ClientSecretExpiryDate *time.Time // Optional expiry date for the client secret (RFC3339 format)
MergeIntervalSeconds int // If non-zero, then overrides the merge interval
DefaultRoles []string // Roles that are activated by default if a user has any one of the AAD roles
RoleToGroup map[string]string // Map from principleName of AAD role, to Authaus group.
AllowArchiveUser bool // If true, then archive users who no longer have the relevant roles in the AAD
PassthroughClientIDs []string // Client IDs of trusted IMQS apps utilising app-to-app passthrough auth
SecretExpiryNotificationDays int // Number of days before expiry to trigger notification (default: 14)
SecretExpiryCheckIntervalHours int // Hours between secret expiry checks (default: 1)
SecretExpiryNotificationCallback ClientSecretExpiryNotificationFunc // Callback function for secret expiry notifications
}

// MSAADInterface
Expand Down Expand Up @@ -222,6 +226,20 @@ func (m *MSAAD) Initialize(parent *Central, log *log.Logger) error {
return fmt.Errorf("MSAAD provider is null")
}

// Run a loop that checks for MSAAD client secret expiry and triggers notifications
go func() {
interval := m.config.SecretExpiryCheckIntervalHours
if interval == 0 {
interval = 1 // Default to 1 hour
}
// Startup grace
time.Sleep(15 * time.Second)
for !m.IsShuttingDown() {
m.checkSecretExpiry()
time.Sleep(time.Duration(interval) * time.Hour)
}
}()

return nil
}

Expand Down Expand Up @@ -756,3 +774,37 @@ func removeFromGroupList(list []GroupIDU32, i int) []GroupIDU32 {
list[i] = list[len(list)-1]
return list[:len(list)-1]
}

// checkSecretExpiry checks the MSAAD client secret for upcoming expiry
// and triggers notifications if it expires within the configured threshold.
func (m *MSAAD) checkSecretExpiry() {
if m.config.SecretExpiryNotificationCallback == nil {
return // No callback configured
}

if m.config.ClientSecretExpiryDate == nil {
return // No expiry date configured
}

notificationDays := m.config.SecretExpiryNotificationDays
if notificationDays == 0 {
notificationDays = 14 // Default to 2 weeks
}

now := time.Now()
threshold := now.Add(time.Duration(notificationDays) * 24 * time.Hour)

if m.config.ClientSecretExpiryDate.Before(threshold) && m.config.ClientSecretExpiryDate.After(now) {
// Calculate days based on date difference, not time difference
nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
expiryDate := time.Date(m.config.ClientSecretExpiryDate.Year(), m.config.ClientSecretExpiryDate.Month(), m.config.ClientSecretExpiryDate.Day(), 0, 0, 0, 0, m.config.ClientSecretExpiryDate.Location())
daysUntilExpiry := int(expiryDate.Sub(nowDate).Hours() / 24)

m.config.SecretExpiryNotificationCallback("MSAAD", daysUntilExpiry, *m.config.ClientSecretExpiryDate)

if m.config.Verbose {
m.log.Warnf("MSAAD client secret expires in %d days (%s)",
daysUntilExpiry, m.config.ClientSecretExpiryDate.Format(time.RFC3339))
}
}
}
83 changes: 68 additions & 15 deletions oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,28 @@ const (
)

type ConfigOAuthProvider struct {
Type string // See OAuthProvider___ constants for legal values
Title string // Name of provider that user sees (probably need an image too)
ClientID string // For MSAAD
LoginURL string // eg https://login.microsoftonline.com/e1ff61b3-a3da-4639-ae31-c6dff3ce7bfb/oauth2/v2.0/authorize
TokenURL string // eg https://login.microsoftonline.com/e1ff61b3-a3da-4639-ae31-c6dff3ce7bfb/oauth2/v2.0/token
RedirectURL string // eg https://stellenbosch.imqs.co.za/auth2/oauth/finish. URL must be listed in IMQS app in Azure. Can be http://localhost/auth2/oauth/finish for testing.
Scope string
ClientSecret string
AllowCreateUser bool // If true, then automatically create an Authaus user for an OAuth user, if the user succeeds in logging in
Type string // See OAuthProvider___ constants for legal values
Title string // Name of provider that user sees (probably need an image too)
ClientID string // For MSAAD
LoginURL string // eg https://login.microsoftonline.com/e1ff61b3-a3da-4639-ae31-c6dff3ce7bfb/oauth2/v2.0/authorize
TokenURL string // eg https://login.microsoftonline.com/e1ff61b3-a3da-4639-ae31-c6dff3ce7bfb/oauth2/v2.0/token
RedirectURL string // eg https://stellenbosch.imqs.co.za/auth2/oauth/finish. URL must be listed in IMQS app in Azure. Can be http://localhost/auth2/oauth/finish for testing.
Scope string
ClientSecret string
ClientSecretExpiryDate *time.Time // Optional expiry date for the client secret (RFC3339 format)
AllowCreateUser bool // If true, then automatically create an Authaus user for an OAuth user, if the user succeeds in logging in
}

type ConfigOAuth struct {
Providers map[string]*ConfigOAuthProvider
Verbose bool // If true, then print a lot of debugging information
ForceFastTokenRefresh bool // If true, then force a token refresh every 120 seconds. This is for testing the token refresh code.
LoginExpirySeconds int64 // A session that starts must be completed within this time period (eg 5 minutes)
TokenCheckIntervalSeconds int // Override interval at which we check that OAuth tokens are still valid, and if not, invalidate the Authaus session. Set to -1 to disable this check.
DefaultProvider string // Can be set to the name of one of the Providers. This was created for the login JS front-end, to act as though the user has pressed the "Sign-in with XYZ" button as soon as the page is loaded.
Providers map[string]*ConfigOAuthProvider
Verbose bool // If true, then print a lot of debugging information
ForceFastTokenRefresh bool // If true, then force a token refresh every 120 seconds. This is for testing the token refresh code.
LoginExpirySeconds int64 // A session that starts must be completed within this time period (eg 5 minutes)
TokenCheckIntervalSeconds int // Override interval at which we check that OAuth tokens are still valid, and if not, invalidate the Authaus session. Set to -1 to disable this check.
DefaultProvider string // Can be set to the name of one of the Providers. This was created for the login JS front-end, to act as though the user has pressed the "Sign-in with XYZ" button as soon as the page is loaded.
SecretExpiryNotificationDays int // Number of days before expiry to trigger notification (default: 14)
SecretExpiryCheckIntervalHours int // Hours between secret expiry checks (default: 1)
SecretExpiryNotificationCallback ClientSecretExpiryNotificationFunc // Callback function for secret expiry notifications
}

type OAuthCompletedResult struct {
Expand Down Expand Up @@ -179,6 +183,20 @@ func (x *OAuth) Initialize(parent *Central) {
}
}()
}

// Run a loop that checks for client secret expiry and triggers notifications
go func() {
interval := x.Config.SecretExpiryCheckIntervalHours
if interval == 0 {
interval = 1 // Default to 1 hour
}
// Startup grace
time.Sleep(10 * time.Second)
for !x.parent.IsShuttingDown() {
x.checkSecretExpiry()
time.Sleep(time.Duration(interval) * time.Hour)
}
}()
}

// HttpHandlerOAuthStart This is a GET or POST request that the frontend calls, in order to start an OAuth login sequence
Expand Down Expand Up @@ -1039,3 +1057,38 @@ func buildPOSTBodyForm(params map[string]string) string {
}
return s
}

// checkSecretExpiry checks all OAuth provider client secrets for upcoming expiry
// and triggers notifications if they expire within the configured threshold.
func (x *OAuth) checkSecretExpiry() {
if x.Config.SecretExpiryNotificationCallback == nil {
return // No callback configured
}

notificationDays := x.Config.SecretExpiryNotificationDays
if notificationDays == 0 {
notificationDays = 14 // Default to 2 weeks
}

now := time.Now()
threshold := now.Add(time.Duration(notificationDays) * 24 * time.Hour)

// Check OAuth providers
for providerName, provider := range x.Config.Providers {
if provider.ClientSecretExpiryDate != nil {
if provider.ClientSecretExpiryDate.Before(threshold) && provider.ClientSecretExpiryDate.After(now) {
// Calculate days based on date difference, not time difference
nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
expiryDate := time.Date(provider.ClientSecretExpiryDate.Year(), provider.ClientSecretExpiryDate.Month(), provider.ClientSecretExpiryDate.Day(), 0, 0, 0, 0, provider.ClientSecretExpiryDate.Location())
daysUntilExpiry := int(expiryDate.Sub(nowDate).Hours() / 24)

x.Config.SecretExpiryNotificationCallback(providerName, daysUntilExpiry, *provider.ClientSecretExpiryDate)

if x.Config.Verbose {
x.parent.Log.Warnf("OAuth provider '%s' client secret expires in %d days (%s)",
providerName, daysUntilExpiry, provider.ClientSecretExpiryDate.Format(time.RFC3339))
}
}
}
}
}
Loading