Skip to content

Commit b678f73

Browse files
normalize subdomain and set it as not secret for c1 compatibility (#16)
* normalize subdomain and set it as not secret for c1 compatibility * fix lint * fix error message * fix lint
1 parent 874b3c1 commit b678f73

File tree

3 files changed

+118
-2
lines changed

3 files changed

+118
-2
lines changed

pkg/config/config.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ var OneLoginSubDomain = field.StringField(
2424
field.WithDisplayName("One Login Subdomain"),
2525
field.WithDescription("OneLogin subdomain to connect to"),
2626
field.WithRequired(true),
27-
field.WithIsSecret(true),
2827
)
2928

3029
var OneLoginEnablePrivileges = field.BoolField(

pkg/connector/connector.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"github.com/conductorone/baton-sdk/pkg/connectorbuilder"
1313
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
1414
"go.uber.org/zap"
15+
"google.golang.org/grpc/codes"
16+
"google.golang.org/grpc/status"
1517
)
1618

1719
var (
@@ -107,11 +109,17 @@ func NewConnector(ctx context.Context, clientId, clientSecret, subdomain string,
107109
// New returns the OneLogin connector configured to sync against the instance URL.
108110
func New(ctx context.Context, config *cfg.Onelogin, opts *cli.ConnectorOpts) (connectorbuilder.ConnectorBuilderV2, []connectorbuilder.Opt, error) {
109111
l := ctxzap.Extract(ctx)
112+
113+
subdomain, err := sanitizeDomainInput(config.Subdomain)
114+
if err != nil {
115+
return nil, nil, status.Errorf(codes.InvalidArgument, "error sanitizing subdomain input: %v", err)
116+
}
117+
110118
cb, err := NewConnector(
111119
ctx,
112120
config.OneloginClientId,
113121
config.OneloginClientSecret,
114-
config.Subdomain,
122+
subdomain,
115123
config.PrivilegesEnabled,
116124
)
117125
if err != nil {

pkg/connector/helpers.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package connector
22

33
import (
4+
"fmt"
5+
"net"
6+
"net/url"
7+
"strings"
8+
49
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
510
"github.com/conductorone/baton-sdk/pkg/annotations"
611
"github.com/conductorone/baton-sdk/pkg/pagination"
@@ -30,3 +35,107 @@ func parsePageToken(i string, resourceID *v2.ResourceId) (*pagination.Bag, strin
3035

3136
return b, b.PageToken(), nil
3237
}
38+
39+
var disallowedSchemesAsHostnames = map[string]bool{
40+
"javascript": true,
41+
"mailto": true,
42+
"ftp": true,
43+
"file": true,
44+
}
45+
46+
const suffix = "onelogin.com"
47+
48+
func sanitizeDomainInput(domain string) (string, error) {
49+
var host string
50+
var port string
51+
52+
if strings.Contains(domain, "//") {
53+
u, err := url.Parse(domain)
54+
if err != nil {
55+
return "", fmt.Errorf("invalid URL format: %w", err)
56+
}
57+
scheme := strings.ToLower(u.Scheme)
58+
59+
if scheme != "http" && scheme != "https" && scheme != "htp" && scheme != "htps" {
60+
return "", fmt.Errorf("unsupported URL scheme: '%s'", u.Scheme)
61+
}
62+
host = u.Hostname()
63+
port = u.Port()
64+
if host == "" {
65+
return "", fmt.Errorf("could not extract hostname from URL: '%s'", domain)
66+
}
67+
} else {
68+
// No "://" found, try to parse as host or host:port
69+
tempHost, tempPort, err := net.SplitHostPort(domain)
70+
if err == nil {
71+
// Successfully split host and port
72+
// Check if the extracted 'host' part looks like a disallowed scheme
73+
if disallowedSchemesAsHostnames[strings.ToLower(tempHost)] {
74+
return "", fmt.Errorf("invalid hostname part, looks like a scheme: '%s'", tempHost)
75+
}
76+
host = tempHost
77+
port = tempPort
78+
} else {
79+
// Could not split host:port, assume the whole input is the host
80+
// But first, check if it contains a colon, which might indicate an invalid scheme attempt without ://
81+
if strings.Contains(domain, ":") {
82+
firstPart := strings.SplitN(domain, ":", 2)[0]
83+
if disallowedSchemesAsHostnames[strings.ToLower(firstPart)] {
84+
return "", fmt.Errorf("invalid input, looks like a scheme without '://': '%s'", firstPart)
85+
}
86+
}
87+
host = domain
88+
}
89+
}
90+
91+
host = strings.TrimSpace(host)
92+
if host == "" {
93+
return "", fmt.Errorf("hostname could not be determined from input: '%s'", domain)
94+
}
95+
96+
host = strings.ToLower(host)
97+
parts := strings.Split(host, ".")
98+
99+
didAppendSuffix := false
100+
// Handle single component expansion (e.g., "acme" -> "acme.okta.com")
101+
if len(parts) == 1 && parts[0] != "" {
102+
host = strings.Join(append(parts, suffix), ".") // Update host for suffix check
103+
didAppendSuffix = true
104+
}
105+
106+
// Suffix validation
107+
108+
matched := false
109+
110+
if host == suffix {
111+
if didAppendSuffix { // Case: single input component + appendSuffix resulted in exact match
112+
matched = true
113+
} else if len(parts) > strings.Count(suffix, ".")+1 {
114+
// Case: multi-component input (or no appendSuffix) was an exact match for suffix
115+
// 'parts' here refers to the components of the original host before any appendSuffix.
116+
matched = true
117+
}
118+
} else if strings.HasSuffix(host, "."+suffix) {
119+
// Check that there's something before the suffix
120+
if len(strings.TrimSuffix(host, "."+suffix)) > 0 {
121+
matched = true
122+
}
123+
}
124+
125+
if !matched {
126+
return "", fmt.Errorf("domain '%s' does not have an expected suffix (e.g., %s)", host, suffix)
127+
}
128+
129+
// Re-split parts after potential single-component expansion for remapping/validation
130+
parts = strings.Split(host, ".")
131+
132+
normalizedHost := strings.Join(parts, ".")
133+
134+
if port != "" && port != "80" && port != "443" {
135+
normalizedHost = net.JoinHostPort(normalizedHost, port)
136+
}
137+
138+
subdomain := strings.TrimSuffix(normalizedHost, ".onelogin.com")
139+
140+
return subdomain, nil
141+
}

0 commit comments

Comments
 (0)