Skip to content

Commit c92e3e4

Browse files
strehleCopilot
andauthored
add sso flow (#119)
* add sso flow * update readme * Update README.md Co-authored-by: Copilot <[email protected]> * Update openid-client/openid-client.go Co-authored-by: Copilot <[email protected]> * Update pkg/client/client.go Co-authored-by: Copilot <[email protected]> * Update pkg/client/client.go Co-authored-by: Copilot <[email protected]> * Update openid-client/openid-client.go Co-authored-by: Copilot <[email protected]> * only init * update * update * update * add silent mode for sso flow --------- Co-authored-by: Copilot <[email protected]>
1 parent c9d1d83 commit c92e3e4

File tree

3 files changed

+89
-14
lines changed

3 files changed

+89
-14
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Command: (authorization_code is default)
4242
passcode Retrieve user passcode from X509 user authentication. Need user_tls for user authentication.
4343
idp_token Retrieve trusted IdP token. Need assertion for user trust and client authentication.
4444
introspect Perform OAuth2 Introspection Endpoint Call. Need token input parameter.
45+
sso Perform sso token flow to create a new web session in IAS.
4546
version Show version.
4647
help Show this help for more details.
4748
@@ -82,6 +83,10 @@ Flags:
8283
-subject_type Token-Exchange subject type. Type of input assertion.
8384
-resource Token-Exchange custom resource parameter.
8485
-requested_type Token-Exchange requested type.
86+
-redirect_uri Redirect URL for the sso command only.
87+
-sp Service provider name parameter for sso command only.
88+
-sso Use sso resource flow. Set true to get static parameter resource=urn:sap:identity:sso. Useful only in token-exchange.
89+
-sso_token Opaque one time token to create a web session in IAS. Useful only in commands sso and authorization_code.
8590
-provider_name Provider name for token-exchange.
8691
-k Skip TLS server certificate verification and skip OIDC issuer check from well-known.
8792
-v Verbose. Show more details about calls.

openid-client/openid-client.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func main() {
4545
" passcode Retrieve user passcode from X509 user authentication. Need user_tls for user authentication.\n" +
4646
" idp_token Retrieve trusted IdP token. Need assertion for user trust and client authentication.\n" +
4747
" introspect Perform OAuth2 Introspection Endpoint Call. Need token input parameter.\n" +
48+
" sso Perform sso token flow to create a new web session in IAS.\n" +
4849
" version Show version.\n" +
4950
" help Show this help for more details.\n" +
5051
"\n" +
@@ -85,6 +86,10 @@ func main() {
8586
" -subject_type Token-Exchange subject type. Type of input assertion.\n" +
8687
" -resource Token-Exchange custom resource parameter.\n" +
8788
" -requested_type Token-Exchange requested type.\n" +
89+
" -redirect_uri Redirect URL for the sso command only.\n" +
90+
" -sp Service provider name parameter for sso command only.\n" +
91+
" -sso Token-Exchange resource SSO flow. Set true to get static parameter resource=urn:sap:identity:sso. Useful only in token-exchange.\n" +
92+
" -sso_token Opaque one time token to create a web session in IAS. Useful only in commands sso and authorization_code.\n" +
8893
" -provider_name Provider name for token-exchange.\n" +
8994
" -k Skip TLS server certificate verification and skip OIDC issuer check from well-known.\n" +
9095
" -v Verbose. Show more details about calls.\n" +
@@ -129,8 +134,12 @@ func main() {
129134
var subjectType = flag.String("subject_type", "", "Token input type")
130135
var requestedType = flag.String("requested_type", "", "Token-Exchange requested type")
131136
var providerName = flag.String("provider_name", "", "Provider name for token-exchange")
137+
var redirectUri = flag.String("redirect_uri", "", "Redirect URL for sso")
138+
var resourceSso = flag.Bool("sso", false, "Adds static parameter resource=urn:sap:identity:sso to token-exchange.")
132139
var resourceParam = flag.String("resource", "", "Additional resource")
133140
var skipTlsVerification = flag.Bool("k", false, "Skip TLS server certificate verification and issuer.")
141+
var ssoTokenValue = flag.String("sso_token", "", "Opaque one time token for sso command.")
142+
var spName = flag.String("sp", "", "Service provider name parameter for sso command only.")
134143
var mTLS = false
135144
var privateKeyJwt = ""
136145
var arguments []string
@@ -153,7 +162,7 @@ func main() {
153162
case "version":
154163
showVersion()
155164
return
156-
case "client_credentials", "password", "token-exchange", "jwt-bearer", "saml-bearer", "idp_token", "":
165+
case "client_credentials", "password", "token-exchange", "jwt-bearer", "saml-bearer", "idp_token", "sso", "":
157166
case "passcode", "introspect":
158167
*clientID = os.Getenv("OPENID_ID")
159168
if *clientID == "" {
@@ -380,7 +389,12 @@ func main() {
380389
originParam, _ := json.Marshal(originStruct)
381390
requestMap.Set("login_hint", url.QueryEscape(string(originParam)))
382391
}
383-
if *providerName != "" {
392+
if *resourceSso {
393+
requestMap.Set("resource", "urn:sap:identity:sso")
394+
requestMap.Set("requested_token_type", "urn:ietf:params:oauth:token-type:access_token")
395+
// Set the requestedType to "access_token" so the caller knows which token type was requested.
396+
*requestedType = "access_token"
397+
} else if *providerName != "" {
384398
requestMap.Set("resource", "urn:sap:identity:application:provider:name:"+*providerName)
385399
}
386400
if *resourceParam != "" {
@@ -430,7 +444,7 @@ func main() {
430444
}
431445
}
432446
if *requestedType == "" {
433-
log.Fatal("assertion parameter not set. Needed to pass it to subject_token for token-exchange")
447+
log.Fatal("requested_type parameter not set. Supported parameters for token-exchange are, id_token, access_token, saml2, saml2-header")
434448
} else {
435449
if strings.Contains(*requestedType, "saml2-header") || strings.Contains(*requestedType, "saml-header") {
436450
requestMap.Set("requested_token_type", "urn:sap:identity:oauth:token-type:saml2-header")
@@ -509,6 +523,8 @@ func main() {
509523
requestMap.Del("client_id")
510524
}
511525
client.HandleTokenIntrospect(requestMap, *tokenInput, claims.IntroSpectEndpoint, *tlsClient, verbose)
526+
} else if *command == "sso" {
527+
client.HandleSsoFlow(*ssoTokenValue, *redirectUri, *spName, *provider)
512528
} else if *command == "jwks" {
513529
}
514530
} else {
@@ -522,7 +538,11 @@ func main() {
522538
if *maxAgeParameter != "" {
523539
requestMap.Set("max_age", *maxAgeParameter)
524540
}
525-
var idToken, refreshToken = client.HandleOpenIDFlow(requestMap, verbose, callbackURL, *scopeParameter, *tokenFormatParameter, *portParameter, claims.EndSessionEndpoint, privateKeyJwt, *provider, *tlsClient)
541+
if *ssoTokenValue != "" {
542+
requestMap.Set("sso_token", *ssoTokenValue)
543+
}
544+
var bSilent = *resourceSso && !verbose
545+
var idToken, refreshToken = client.HandleOpenIDFlow(requestMap, verbose, bSilent, callbackURL, *scopeParameter, *tokenFormatParameter, *portParameter, claims.EndSessionEndpoint, privateKeyJwt, *provider, *tlsClient)
526546
if *doRefresh {
527547
if refreshToken == "" {
528548
log.Println("No refresh token received.")
@@ -550,6 +570,13 @@ func main() {
550570
}
551571
fmt.Println(string(data))
552572
}
573+
if *resourceSso {
574+
// Set the requestedType to "access_token" so the caller knows which token type was requested.
575+
*requestedType = "access_token"
576+
*resourceParam = ""
577+
requestMap.Set("resource", "urn:sap:identity:sso")
578+
requestMap.Set("requested_token_type", "urn:ietf:params:oauth:token-type:access_token")
579+
}
553580
if *requestedType != "" && idToken != "" {
554581
requestMap.Set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
555582
requestMap.Set("subject_token_type", "urn:ietf:params:oauth:token-type:id_token")
@@ -567,7 +594,11 @@ func main() {
567594
}
568595

569596
var exchangedTokenResponse = client.HandleTokenExchangeGrant(requestMap, claims.TokenEndPoint, *tlsClient, verbose)
570-
fmt.Println(exchangedTokenResponse)
597+
if exchangedTokenResponse.AccessToken != "" {
598+
fmt.Println(exchangedTokenResponse.AccessToken)
599+
} else {
600+
fmt.Println(exchangedTokenResponse.IdToken)
601+
}
571602
}
572603
if *doIntrospect && idToken != "" && claims.IntroSpectEndpoint != "" {
573604
requestMap := url.Values{}

pkg/client/client.go

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (h *callbackEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
6464
h.shutdownSignal <- "shutdown"
6565
}
6666

67-
func HandleOpenIDFlow(request url.Values, verbose bool, callbackURL string, scopeParameter string, tokenFormatParameter string, port string, endsession string, privateKeyJwt string, provider oidc.Provider, tlsClient http.Client) (string, string) {
67+
func HandleOpenIDFlow(request url.Values, verbose bool, bSilent bool, callbackURL string, scopeParameter string, tokenFormatParameter string, port string, endsession string, privateKeyJwt string, provider oidc.Provider, tlsClient http.Client) (string, string) {
6868

6969
refreshToken := ""
7070
idToken := ""
@@ -123,10 +123,14 @@ func HandleOpenIDFlow(request url.Values, verbose bool, callbackURL string, scop
123123
if request.Has("max_age") {
124124
query.Set("max_age", request.Get("max_age"))
125125
}
126+
if request.Has("sso_token") {
127+
query.Set("sso_token", request.Get("sso_token"))
128+
}
126129
authzURL.RawQuery = query.Encode()
127130

128-
//cmd := exec.Command("open", authzURL.String())
129-
fmt.Println("Execute URL: ", authzURL.String())
131+
if !bSilent {
132+
fmt.Println("Execute URL: ", authzURL.String())
133+
}
130134

131135
cmd := exec.Command("", authzURL.String())
132136
switch runtime.GOOS {
@@ -196,13 +200,14 @@ func HandleOpenIDFlow(request url.Values, verbose bool, callbackURL string, scop
196200
}
197201

198202
if resp.StatusCode == 200 && result != nil {
199-
fmt.Println("==========")
200-
if verbose {
201-
fmt.Println("OIDC Response Body")
203+
if !bSilent {
204+
fmt.Println("==========")
205+
if verbose {
206+
fmt.Println("OIDC Response Body")
207+
}
208+
showHttpClientError(result)
209+
fmt.Println("==========")
202210
}
203-
showHttpClientError(result)
204-
fmt.Println("==========")
205-
206211
var jsonStr = result
207212
ctx := context.Background()
208213
var myToken OIDC_Token
@@ -514,3 +519,37 @@ func showHttpError(response http.Response) {
514519
log.Fatalln("HTTP 500 received")
515520
}
516521
}
522+
523+
func HandleSsoFlow(ssoToken string, redirectUri string, spName string, provider oidc.Provider) (string, string) {
524+
525+
authzURL, authzURLParseError := url.Parse(provider.Endpoint().AuthURL)
526+
if authzURLParseError != nil {
527+
log.Fatal(authzURLParseError)
528+
}
529+
query := authzURL.Query()
530+
query.Set("redirect_uri", redirectUri)
531+
query.Set("sso_token", ssoToken)
532+
if spName != "" {
533+
query.Set("sp", spName)
534+
}
535+
authzURL.RawQuery = query.Encode()
536+
openUrl := strings.Replace(authzURL.String(), "/oauth2/authorize", "/saml2/idp/sso", 1)
537+
var cmd *exec.Cmd
538+
switch runtime.GOOS {
539+
case "linux":
540+
cmd = exec.Command("xdg-open", openUrl)
541+
case "windows":
542+
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", openUrl)
543+
case "darwin":
544+
cmd = exec.Command("open", openUrl)
545+
default:
546+
cmd = nil
547+
fmt.Println("unsupported platform")
548+
return "", ""
549+
}
550+
cmdError := cmd.Start()
551+
if cmdError != nil {
552+
log.Fatal(cmdError)
553+
}
554+
return "", ""
555+
}

0 commit comments

Comments
 (0)