github.com/greenpau/go-authcrunch@v1.0.50/pkg/authn/handle_http_settings.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 "github.com/greenpau/go-authcrunch/pkg/requests" 21 "github.com/greenpau/go-authcrunch/pkg/user" 22 addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr" 23 "go.uber.org/zap" 24 "net/http" 25 "strings" 26 ) 27 28 func (p *Portal) handleHTTPSettings(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, parsedUser *user.User) error { 29 p.disableClientCache(w) 30 p.injectRedirectURL(ctx, w, r, rr) 31 if parsedUser == nil { 32 if rr.Response.RedirectURL == "" { 33 return p.handleHTTPRedirect(ctx, w, r, rr, "/login?redirect_url="+r.RequestURI) 34 } 35 return p.handleHTTPRedirect(ctx, w, r, rr, "/login") 36 } 37 38 usr, err := p.sessions.Get(parsedUser.Claims.ID) 39 if err != nil { 40 p.logger.Warn( 41 "jti session not found", 42 zap.String("session_id", rr.Upstream.SessionID), 43 zap.String("request_id", rr.ID), 44 zap.String("jti", parsedUser.Claims.ID), 45 zap.Any("error", err), 46 zap.String("source_address", addrutil.GetSourceAddress(r)), 47 ) 48 return p.handleHTTPLogoutWithLocalRedirect(ctx, w, r, rr) 49 } 50 51 if permitted := usr.HasRole("authp/admin", "authp/user"); !permitted { 52 return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) 53 } 54 55 switch usr.Authenticator.Method { 56 case "local": 57 default: 58 return p.handleHTTPGeneric(ctx, w, r, rr, http.StatusServiceUnavailable, http.StatusText(http.StatusServiceUnavailable)) 59 } 60 61 endpoint, err := getEndpoint(r.URL.Path, "/settings") 62 if err != nil { 63 return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) 64 } 65 66 backend := p.getIdentityStoreByRealm(usr.Authenticator.Realm) 67 if backend == nil { 68 p.logger.Warn( 69 "backend not found", 70 zap.String("session_id", rr.Upstream.SessionID), 71 zap.String("request_id", rr.ID), 72 zap.String("realm", usr.Authenticator.Realm), 73 zap.String("jti", parsedUser.Claims.ID), 74 zap.String("source_address", addrutil.GetSourceAddress(r)), 75 ) 76 return p.handleHTTPLogoutWithLocalRedirect(ctx, w, r, rr) 77 } 78 79 p.logger.Debug( 80 "Rendering settings page", 81 zap.String("session_id", rr.Upstream.SessionID), 82 zap.String("request_id", rr.ID), 83 zap.String("realm", usr.Authenticator.Realm), 84 zap.String("jti", parsedUser.Claims.ID), 85 zap.String("source_address", addrutil.GetSourceAddress(r)), 86 zap.String("endpoint", endpoint), 87 ) 88 89 resp := p.ui.GetArgs() 90 resp.PageTitle = "Profile" 91 resp.BaseURL(rr.Upstream.BasePath) 92 93 // Populate username (sub) and email address (email) 94 rr.User.Username = usr.Claims.Subject 95 rr.User.Email = usr.Claims.Email 96 97 switch { 98 case strings.HasPrefix(endpoint, "/password"): 99 resp.PageTitle = "Password Management" 100 resp.NavItems = p.config.UI.GetNavigationItems("settings/password") 101 if p.config.UI.IsDisabledPage("settings/password") { 102 return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) 103 } 104 if err := p.handleHTTPPasswordSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { 105 return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) 106 } 107 case strings.HasPrefix(endpoint, "/apikeys"): 108 resp.PageTitle = "API Key Management" 109 resp.NavItems = p.config.UI.GetNavigationItems("settings/apikeys") 110 if p.config.UI.IsDisabledPage("settings/apikeys") { 111 return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) 112 } 113 if err := p.handleHTTPAPIKeysSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { 114 return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) 115 } 116 case strings.HasPrefix(endpoint, "/sshkeys"): 117 resp.PageTitle = "SSH Key Management" 118 resp.NavItems = p.config.UI.GetNavigationItems("settings/sshkeys") 119 if p.config.UI.IsDisabledPage("settings/sshkeys") { 120 return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) 121 } 122 if err := p.handleHTTPSSHKeysSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { 123 return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) 124 } 125 case strings.HasPrefix(endpoint, "/gpgkeys"): 126 resp.PageTitle = "GPG Key Management" 127 resp.NavItems = p.config.UI.GetNavigationItems("settings/gpgkeys") 128 if p.config.UI.IsDisabledPage("settings/gpgkeys") { 129 return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) 130 } 131 if err := p.handleHTTPGPGKeysSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { 132 return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) 133 } 134 case strings.HasPrefix(endpoint, "/mfa/barcode/"): 135 return p.handleHTTPMfaBarcode(ctx, w, r, endpoint) 136 case strings.HasPrefix(endpoint, "/mfa"): 137 resp.PageTitle = "Multi-Factor Authentication" 138 resp.NavItems = p.config.UI.GetNavigationItems("settings/mfa") 139 if p.config.UI.IsDisabledPage("settings/mfa") { 140 return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) 141 } 142 if err := p.handleHTTPMfaSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { 143 return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) 144 } 145 case strings.HasPrefix(endpoint, "/connected"): 146 resp.PageTitle = "Connected Accounts" 147 resp.NavItems = p.config.UI.GetNavigationItems("settings/connected") 148 if p.config.UI.IsDisabledPage("settings/connected") { 149 return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden) 150 } 151 resp.Data["view"] = "connected" 152 default: 153 resp.NavItems = p.config.UI.GetNavigationItems("settings/") 154 if err := p.handleHTTPGeneralSettings(ctx, r, rr, usr, backend, resp.Data); err != nil { 155 return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest) 156 } 157 } 158 content, err := p.ui.Render("settings", resp) 159 if err != nil { 160 return p.handleHTTPRenderError(ctx, w, r, rr, err) 161 } 162 return p.handleHTTPRenderHTML(ctx, w, http.StatusOK, content.Bytes()) 163 } 164 165 func getEndpoint(p, s string) (string, error) { 166 i := strings.Index(p, s) 167 if i < 0 { 168 return s, fmt.Errorf("%s is not in %s", p, s) 169 } 170 return strings.TrimPrefix(p[i:], s), nil 171 } 172 173 func getEndpointKeyID(p, s string) (string, error) { 174 sp, err := getEndpoint(p, s) 175 if err != nil { 176 return "", err 177 } 178 arr := strings.Split(sp, "/") 179 if len(arr) != 1 { 180 return "", fmt.Errorf("invalid key id") 181 } 182 return arr[0], nil 183 } 184 185 func attachView(data map[string]interface{}, entrypoint, action string, status bool) { 186 if action == "" { 187 data["view"] = entrypoint 188 return 189 } 190 if status { 191 data["view"] = fmt.Sprintf("%s-%s-status", entrypoint, action) 192 return 193 } 194 data["view"] = fmt.Sprintf("%s-%s", entrypoint, action) 195 } 196 197 func attachStatus(data map[string]interface{}, status, statusText string) { 198 data["status"] = status 199 data["status_reason"] = statusText 200 } 201 202 func attachSuccessStatus(data map[string]interface{}, statusText string) { 203 attachStatus(data, "SUCCESS", statusText) 204 } 205 206 func attachFailStatus(data map[string]interface{}, statusText string) { 207 attachStatus(data, "FAIL", statusText) 208 }