github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/handle_http_apps_sso.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package authn 16 17 import ( 18 "context" 19 "fmt" 20 "net/http" 21 "strings" 22 23 "github.com/greenpau/go-authcrunch/pkg/requests" 24 "github.com/greenpau/go-authcrunch/pkg/sso" 25 "github.com/greenpau/go-authcrunch/pkg/user" 26 "go.uber.org/zap" 27 ) 28 29 type assumeRoleEntry struct { 30 Name string 31 AccountID string 32 ProviderName string 33 } 34 35 func (p *Portal) handleHTTPAppsSingleSignOn(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, parsedUser *user.User) error { 36 p.disableClientCache(w) 37 p.injectRedirectURL(ctx, w, r, rr) 38 39 if parsedUser == nil { 40 if rr.Response.RedirectURL == "" { 41 return p.handleHTTPRedirect(ctx, w, r, rr, "/login?redirect_url="+r.RequestURI) 42 } 43 return p.handleHTTPRedirect(ctx, w, r, rr, "/login") 44 } 45 46 usr, err := p.sessions.Get(parsedUser.Claims.ID) 47 if err != nil { 48 p.deleteAuthCookies(w, r) 49 p.logger.Debug( 50 "User session not found, redirect to login", 51 zap.String("session_id", rr.Upstream.SessionID), 52 zap.String("request_id", rr.ID), 53 zap.Any("user", parsedUser.Claims), 54 zap.Error(err), 55 ) 56 if rr.Response.RedirectURL == "" { 57 return p.handleHTTPRedirect(ctx, w, r, rr, "/login?redirect_url="+r.RequestURI) 58 } 59 return p.handleHTTPRedirect(ctx, w, r, rr, "/login") 60 } 61 62 // Parse SSO provider name from URL. 63 req, err := sso.ParseRequestURL(r) 64 if err != nil { 65 return p.handleHTTPRenderError(ctx, w, r, rr, err) 66 } 67 68 // Check whether the requested SSO provider exists. 69 provider, err := p.fetchSingleSignOnProvider(req.ProviderName) 70 if err != nil { 71 return p.handleHTTPRenderError(ctx, w, r, rr, err) 72 } 73 74 roles := fetchSingleSignOnRoles(provider.GetName(), usr) 75 76 switch req.Kind { 77 case sso.MetadataRequest: 78 return p.handleHTTPAppsSingleSignOnMetadata(ctx, w, r, rr, provider, roles) 79 case sso.AssumeRoleRequest: 80 return p.handleHTTPAppsSingleSignOnAssumeRole(ctx, w, r, rr, provider, roles, usr) 81 case sso.MenuRequest: 82 return p.handleHTTPAppsSingleSignOnMenu(ctx, w, r, rr, provider, roles, usr) 83 } 84 return p.handleHTTPAppsSingleSignOnMenu(ctx, w, r, rr, provider, roles, usr) 85 } 86 87 // handleHTTPAppsSingleSignOnMetadata renders metadata.xml content. It is only available to admin users. 88 func (p *Portal) handleHTTPAppsSingleSignOnMetadata(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, 89 provider sso.SingleSignOnProvider, roles []*assumeRoleEntry) error { 90 metadata, err := provider.GetMetadata() 91 if err != nil { 92 return p.handleHTTPRenderError(ctx, w, r, rr, err) 93 } 94 w.Header().Set("Content-Type", "application/xml") 95 w.WriteHeader(http.StatusOK) 96 w.Write(metadata) 97 return nil 98 } 99 100 func (p *Portal) handleHTTPAppsSingleSignOnAssumeRole(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, 101 provider sso.SingleSignOnProvider, roles []*assumeRoleEntry, usr *user.User) error { 102 103 /* 104 if strings.Contains(r.URL.Path, "/apps/sso/assume") { 105 accountRole, err := getEndpoint(r.URL.Path, "/apps/sso/assume/") 106 if err != nil { 107 p.logger.Warn( 108 "SSO request failed", 109 zap.String("session_id", rr.Upstream.SessionID), 110 zap.String("request_id", rr.ID), 111 zap.String("error", "malformed SSO request"), 112 ) 113 } else { 114 assumedRole = true 115 arr := strings.SplitN(accountRole, "/", 2) 116 if len(arr) != 2 { 117 return p.handleHTTPRenderError(ctx, w, r, rr, fmt.Errorf("Malformed SSO request")) 118 } 119 accountID = arr[0] 120 roleName = arr[1] 121 } 122 } 123 */ 124 125 /* 126 if assumedRole { 127 if (role.Name == roleName) && (role.AccountID == accountID) { 128 authorizedRole = true 129 p.logger.Debug( 130 "SSO assume role request received", 131 zap.String("session_id", rr.Upstream.SessionID), 132 zap.String("request_id", rr.ID), 133 zap.String("role_name", roleName), 134 zap.String("account_id", accountID), 135 ) 136 } 137 } 138 */ 139 140 /* 141 if assumedRole { 142 if !authorizedRole { 143 p.logger.Debug( 144 "Unauthorized SSO assume role request", 145 zap.String("session_id", rr.Upstream.SessionID), 146 zap.String("request_id", rr.ID), 147 zap.String("role_name", roleName), 148 zap.String("account_id", accountID), 149 ) 150 return p.handleHTTPRenderError(ctx, w, r, rr, fmt.Errorf("Unauthorized SSO assume role request")) 151 } 152 p.logger.Debug("Redirecting to SAML endpoint") 153 } 154 155 */ 156 157 body := []byte("ASSUME ROLE") 158 w.Header().Set("Content-Type", "text/html") 159 w.WriteHeader(http.StatusOK) 160 w.Write(body) 161 return nil 162 } 163 164 // handleHTTPAppsSingleSignOnMenu renders SSO provider role selection page. 165 func (p *Portal) handleHTTPAppsSingleSignOnMenu(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, 166 provider sso.SingleSignOnProvider, roles []*assumeRoleEntry, usr *user.User) error { 167 168 resp := p.ui.GetArgs() 169 resp.PageTitle = "AWS SSO" 170 resp.BaseURL(rr.Upstream.BasePath) 171 resp.Data["role_count"] = len(roles) 172 resp.Data["roles"] = roles 173 174 content, err := p.ui.Render("apps_sso", resp) 175 if err != nil { 176 return p.handleHTTPRenderError(ctx, w, r, rr, err) 177 } 178 return p.handleHTTPRenderHTML(ctx, w, http.StatusOK, content.Bytes()) 179 } 180 181 func (p *Portal) fetchSingleSignOnProvider(providerName string) (sso.SingleSignOnProvider, error) { 182 for _, provider := range p.ssoProviders { 183 if provider.GetName() == providerName { 184 return provider, nil 185 } 186 } 187 return nil, fmt.Errorf("provider name not found") 188 } 189 190 func (p *Portal) parseSingleSignOnProviderName() (string, string, error) { 191 return "aws", "metadata", nil 192 } 193 194 func fetchSingleSignOnRoles(providerName string, usr *user.User) []*assumeRoleEntry { 195 roles := []*assumeRoleEntry{} 196 for _, entry := range usr.Claims.Roles { 197 arr := strings.Split(entry, "/") 198 if len(arr) != 3 { 199 continue 200 } 201 if arr[0] != "aws" { 202 continue 203 } 204 role := &assumeRoleEntry{ 205 Name: arr[2], 206 AccountID: arr[1], 207 ProviderName: providerName, 208 } 209 roles = append(roles, role) 210 } 211 return roles 212 }