func handleChangePasswordFunc(o *OtsimoAccounts) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { r.ParseForm() oldPass := r.PostFormValue("old_password") newPass := r.PostFormValue("new_password") userID := r.PostFormValue("user_id") if userID == "" || oldPass == "" || newPass == "" { log.Infof("update.go: invalid body '%s'", userID) writeError(w, http.StatusBadRequest, "invalid body") return } resp, err := o.Dex.ChangePassword(context.Background(), &pb.ChangePasswordRequest{ UserId: userID, OldPassword: oldPass, NewPassword: newPass, }) log.Infof("update.go: change password result is %q %v", resp, err) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeResponseWithBody(w, http.StatusOK, resp) } else { writeError(w, http.StatusNotFound, "Not Found") } } }
func handleChangeEmailFunc(o *OtsimoAccounts) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { r.ParseForm() oldEmail := r.PostFormValue("old_email") newEmail := r.PostFormValue("new_email") if oldEmail == "" || newEmail == "" || oldEmail == newEmail { log.Info("update.go: invalid body") writeError(w, http.StatusBadRequest, "invalid body") return } resp, err := o.Dex.ChangeEmail(context.Background(), &pb.ChangeEmailRequest{ OldEmail: oldEmail, NewEmail: newEmail, }) log.Infof("update.go: change email result is %q %v", resp, err) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } id := r.Header.Get("sub") if bson.IsObjectIdHex(id) { o.Api.UpdateProfile(context.Background(), &apipb.Profile{Id: bson.ObjectIdHex(id), Email: newEmail}) } writeResponseWithBody(w, http.StatusOK, resp) } else { writeError(w, http.StatusNotFound, "Not Found") } } }
func registerUser(o *OtsimoAccounts, email, password, firstName, lastName, language string) (*pb.RegisterResponse, error) { resp, err := o.Dex.Register(context.Background(), &pb.RegisterRequest{ Email: email, DisplayName: fmt.Sprintf("%s %s", firstName, lastName), Password: password, }) log.Infof("register.go: register result of '%s' is %q %v", email, resp, err) if err != nil { return nil, err } _, errapi := o.Api.AddProfile(context.Background(), &apipb.Profile{ Id: bson.ObjectIdHex(resp.UserId), Email: email, FirstName: firstName, LastName: lastName, Language: language, }) if errapi != nil { //Disable or delete user log.Errorf("register.go: failed to add profile %+v", errapi) _, err = o.Dex.RemoveUser(context.Background(), &pb.RemoveRequest{Id: resp.UserId, Email: email}) if err != nil { log.Errorf("register.go: Holly F**K!!: failed to add profile and remove user [error]=%+v [user_id]='%s' [user_email]='%s'", err, resp.UserId, email) return nil, err } return nil, fmt.Errorf("failed to register user, adding to api service failed:%v", errapi) } return resp, nil }
func (s *Server) ClientCredsToken(creds oidc.ClientCredentials) (*jose.JWT, error) { ok, err := s.ClientIdentityRepo.Authenticate(creds) if err != nil { log.Errorf("Failed fetching client %s from repo: %v", creds.ID, err) return nil, oauth2.NewError(oauth2.ErrorServerError) } if !ok { return nil, oauth2.NewError(oauth2.ErrorInvalidClient) } signer, err := s.KeyManager.Signer() if err != nil { log.Errorf("Failed to generate ID token: %v", err) return nil, oauth2.NewError(oauth2.ErrorServerError) } now := time.Now() exp := now.Add(s.SessionManager.ValidityWindow) claims := oidc.NewClaims(s.IssuerURL.String(), creds.ID, creds.ID, now, exp) claims.Add("name", creds.ID) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { log.Errorf("Failed to generate ID token: %v", err) return nil, oauth2.NewError(oauth2.ErrorServerError) } log.Infof("Client token sent: clientID=%s", creds.ID) return jwt, nil }
func (gc *GarbageCollector) Run() chan struct{} { stop := make(chan struct{}) go func() { var failing bool next := gc.interval for { select { case <-gc.clock.After(next): if anyPurgeErrors(purgeAll(gc.purgers)) { if !failing { failing = true next = time.Second } else { next = ptime.ExpBackoff(next, time.Minute) } log.Errorf("Failed garbage collection, retrying in %v", next) } else { failing = false next = gc.interval log.Infof("Garbage collection complete, running again in %v", next) } case <-stop: return } } }() return stop }
func (s *Server) NewSession(ipdcID, clientID, clientState string, redirectURL url.URL, nonce string, register bool, scope []string) (string, error) { sessionID, err := s.SessionManager.NewSession(ipdcID, clientID, clientState, redirectURL, nonce, register, scope) if err != nil { return "", err } log.Infof("Session %s created: clientID=%s clientState=%s", sessionID, clientID, clientState) return s.SessionManager.NewSessionKey(sessionID) }
func (r *pcsStepRetry) step(fn pcsStepFunc) (next pcsStepper) { ttl, err := fn() if err == nil { next = &pcsStepNext{aft: ttl} log.Infof("JWT refresh no longer failing") } else { next = &pcsStepRetry{aft: timeutil.ExpBackoff(r.aft, time.Minute)} log.Errorf("JWT refresh still failing, retrying in %v: %v", next.after(), err) } return }
func (s *Server) RefreshToken(creds oidc.ClientCredentials, token string) (*jose.JWT, error) { ok, err := s.ClientIdentityRepo.Authenticate(creds) if err != nil { log.Errorf("Failed fetching client %s from repo: %v", creds.ID, err) return nil, oauth2.NewError(oauth2.ErrorServerError) } if !ok { log.Errorf("Failed to Authenticate client %s", creds.ID) return nil, oauth2.NewError(oauth2.ErrorInvalidClient) } userID, err := s.RefreshTokenRepo.Verify(creds.ID, token) switch err { case nil: break case refresh.ErrorInvalidToken: return nil, oauth2.NewError(oauth2.ErrorInvalidRequest) case refresh.ErrorInvalidClientID: return nil, oauth2.NewError(oauth2.ErrorInvalidClient) default: return nil, oauth2.NewError(oauth2.ErrorServerError) } user, err := s.UserRepo.Get(nil, userID) if err != nil { // The error can be user.ErrorNotFound, but we are not deleting // user at this moment, so this shouldn't happen. log.Errorf("Failed to fetch user %q from repo: %v: ", userID, err) return nil, oauth2.NewError(oauth2.ErrorServerError) } signer, err := s.KeyManager.Signer() if err != nil { log.Errorf("Failed to refresh ID token: %v", err) return nil, oauth2.NewError(oauth2.ErrorServerError) } now := time.Now() expireAt := now.Add(session.DefaultSessionValidityWindow) claims := oidc.NewClaims(s.IssuerURL.String(), user.ID, creds.ID, now, expireAt) user.AddToClaims(claims) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { log.Errorf("Failed to generate ID token: %v", err) return nil, oauth2.NewError(oauth2.ErrorServerError) } log.Infof("New token sent: clientID=%s", creds.ID) return jwt, nil }
func (s *Server) AddConnector(cfg connector.ConnectorConfig) error { connectorID := cfg.ConnectorID() ns := s.IssuerURL ns.Path = path.Join(ns.Path, httpPathAuth, connectorID) idpc, err := cfg.Connector(ns, s.Login, s.Templates) if err != nil { return err } s.Connectors = append(s.Connectors, idpc) sortable := sortableIDPCs(s.Connectors) sort.Sort(sortable) // We handle the LocalConnector specially because it needs access to the // UserRepo and the PasswordInfoRepo; if it turns out that other connectors // need access to these resources we'll figure out how to provide it in a // cleaner manner. localConn, ok := idpc.(*connector.LocalConnector) if ok { s.localConnectorID = connectorID if s.UserRepo == nil { return errors.New("UserRepo cannot be nil") } if s.PasswordInfoRepo == nil { return errors.New("PasswordInfoRepo cannot be nil") } localConn.SetLocalIdentityProvider(&connector.LocalIdentityProvider{ UserRepo: s.UserRepo, PasswordInfoRepo: s.PasswordInfoRepo, }) localCfg, ok := cfg.(*connector.LocalConnectorConfig) if !ok { return errors.New("config for LocalConnector not a LocalConnectorConfig?") } if len(localCfg.PasswordInfos) > 0 { err := user.LoadPasswordInfos(s.PasswordInfoRepo, localCfg.PasswordInfos) if err != nil { return err } } } log.Infof("Loaded IdP connector: id=%s type=%s", connectorID, cfg.ConnectorType()) return nil }
func (m *mailgunEmailer) SendMail(subject, text, html string, to ...string) error { msg := m.mg.NewMessage(m.from, subject, text, to...) if html != "" { msg.SetHtml(html) } mes, id, err := m.mg.Send(msg) if err != nil { counterEmailSendErr.Add(1) return err } log.Infof("SendMail: msgID: %v: %q", id, mes) return nil }
func (u *UsersAPI) DisableUser(creds Creds, userID string, disable bool) (schema.UserDisableResponse, error) { log.Infof("userAPI: DisableUser") if !u.Authorize(creds) { return schema.UserDisableResponse{}, ErrorUnauthorized } if err := u.manager.Disable(userID, disable); err != nil { return schema.UserDisableResponse{}, mapError(err) } return schema.UserDisableResponse{ Ok: true, }, nil }
func (u *UsersAPI) GetUser(creds Creds, id string) (schema.User, error) { log.Infof("userAPI: GetUser") if !u.Authorize(creds) { return schema.User{}, ErrorUnauthorized } usr, err := u.userManager.Get(id) if err != nil { return schema.User{}, mapError(err) } return userToSchemaUser(usr), nil }
func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (schema.UserCreateResponse, error) { log.Infof("userAPI: CreateUser") if !u.Authorize(creds) { return schema.UserCreateResponse{}, ErrorUnauthorized } hash, err := generateTempHash() if err != nil { return schema.UserCreateResponse{}, mapError(err) } metadata, err := u.clientIdentityRepo.Metadata(creds.ClientID) if err != nil { return schema.UserCreateResponse{}, mapError(err) } validRedirURL, err := client.ValidRedirectURL(&redirURL, metadata.RedirectURLs) if err != nil { return schema.UserCreateResponse{}, ErrorInvalidRedirectURL } id, err := u.manager.CreateUser(schemaUserToUser(usr), user.Password(hash), u.localConnectorID) if err != nil { return schema.UserCreateResponse{}, mapError(err) } userUser, err := u.manager.Get(id) if err != nil { return schema.UserCreateResponse{}, mapError(err) } usr = userToSchemaUser(userUser) url, err := u.emailer.SendInviteEmail(usr.Email, validRedirURL, creds.ClientID) // An email is sent only if we don't get a link and there's no error. emailSent := err == nil && url == nil var resetLink string if url != nil { resetLink = url.String() } return schema.UserCreateResponse{ User: &usr, EmailSent: emailSent, ResetPasswordLink: resetLink, }, nil }
func handleRegisterFunc(o *OtsimoAccounts) http.HandlerFunc { handlePOST := func(w http.ResponseWriter, r *http.Request) { r.ParseForm() email := r.PostFormValue("username") password := r.PostFormValue("password") firstName := r.PostFormValue("first_name") lastName := r.PostFormValue("last_name") language := r.PostFormValue("language") if email == "" || password == "" { writeError(w, http.StatusBadRequest, "invalid body") return } result, err := registerUser(o, email, password, firstName, lastName, language) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeResponseWithBody(w, http.StatusOK, result.Token) } handleGET := func(w http.ResponseWriter, r *http.Request) { oac, err := o.Oidc.OAuthClient() if err != nil { log.Errorf("failed to create OAuthClient %v", err) writeError(w, http.StatusInternalServerError, err.Error()) } u, err := url.Parse(oac.AuthCodeURL("", "", "")) q := u.Query() q.Set("register", "1") if err != nil { log.Errorf("failed to create authCoreURL %v", err) writeError(w, http.StatusInternalServerError, err.Error()) } u.RawQuery = q.Encode() log.Infof("URL: %v", u.String()) http.Redirect(w, r, u.String(), http.StatusFound) } return func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { handlePOST(w, r) } else if r.Method == "GET" { handleGET(w, r) } else { writeError(w, http.StatusNotFound, "wrong Http Verb") } } }
func (s *grpcServer) authToken(jwt jose.JWT) (string, *oidc.ClientMetadata, error) { ciRepo := s.server.ClientIdentityRepo keys, err := s.server.KeyManager.PublicKeys() if err != nil { log.Errorf("grpc.go: Failed to get keys: %v", err) return "", nil, errors.New("errorAccessDenied") } if len(keys) == 0 { log.Error("grpc.go: No keys available for verification client") return "", nil, errors.New("errorAccessDenied") } ok, err := oidc.VerifySignature(jwt, keys) if err != nil { log.Errorf("grpc.go: Failed to verify signature: %v", err) return "", nil, err } if !ok { log.Info("grpc.go: token signature is not verified") return "", nil, errors.New("invalid token") } clientID, err := oidc.VerifyClientClaims(jwt, s.server.IssuerURL.String()) if err != nil { log.Errorf("grpc.go: Failed to verify JWT claims: %v", err) return "", nil, errors.New("failed to verify jwt claims token") } md, err := ciRepo.Metadata(clientID) if md == nil || err != nil { log.Errorf("grpc.go: Failed to find clientID: %s, error=%v", clientID, err) return "", nil, err } //client must be admin in order to use login and register grpc apis. ok, err = ciRepo.IsDexAdmin(clientID) if err != nil { return "", nil, err } if !ok { log.Infof("grpc.go: Client [%s] is not admin", clientID) return "", nil, errors.New("errorAccessDenied") } log.Debugf("grpc.go: Authenticated token for client ID %s", clientID) return clientID, md, nil }
func handleLoginFunc(o *OtsimoAccounts) http.HandlerFunc { handlePOST := func(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.PostFormValue("username") password := r.PostFormValue("password") grant_type := r.PostFormValue("grant_type") if username == "" || password == "" || grant_type == "" { writeError(w, http.StatusBadRequest, "invalid body") return } resp, err := o.Dex.Login(context.Background(), &pb.LoginRequest{ GrantType: grant_type, BasicAuth: "Basic " + basicAuth(username, password), }) log.Infof("login.go: login result of '%s' is %q %v", username, resp, err) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeResponseWithBody(w, http.StatusOK, resp) } handleGET := func(w http.ResponseWriter, r *http.Request) { oac, err := o.Oidc.OAuthClient() if err != nil { writeError(w, http.StatusInternalServerError, "unabled create oauth client") return } u, err := url.Parse(oac.AuthCodeURL("", "", "")) if err != nil { writeError(w, http.StatusInternalServerError, "unabled create auth code url") return } http.Redirect(w, r, u.String(), http.StatusFound) } return func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { handlePOST(w, r) } else if r.Method == "GET" { handleGET(w, r) } else { writeError(w, http.StatusNotFound, "wrong HTTP Verb") } } }
func handleRegisterFunc(c *oidc.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { oac, err := c.OAuthClient() if err != nil { panic("unable to proceed") } u, err := url.Parse(oac.AuthCodeURL("", "", "")) q := u.Query() q.Set("register", "1") if err != nil { panic("unable to proceed") } u.RawQuery = q.Encode() log.Infof("URL: %v", u.String()) http.Redirect(w, r, u.String(), http.StatusFound) } }
func (u *UsersAPI) ResendEmailInvitation(creds Creds, userID string, redirURL url.URL) (schema.ResendEmailInvitationResponse, error) { log.Infof("userAPI: ResendEmailInvitation") if !u.Authorize(creds) { return schema.ResendEmailInvitationResponse{}, ErrorUnauthorized } metadata, err := u.clientIdentityRepo.Metadata(creds.ClientID) if err != nil { return schema.ResendEmailInvitationResponse{}, mapError(err) } validRedirURL, err := client.ValidRedirectURL(&redirURL, metadata.RedirectURIs) if err != nil { return schema.ResendEmailInvitationResponse{}, ErrorInvalidRedirectURL } // Retrieve user to check if it's already created userUser, err := u.manager.Get(userID) if err != nil { return schema.ResendEmailInvitationResponse{}, mapError(err) } // Check if email is verified if userUser.EmailVerified { return schema.ResendEmailInvitationResponse{}, ErrorVerifiedEmail } url, err := u.emailer.SendInviteEmail(userUser.Email, validRedirURL, creds.ClientID) // An email is sent only if we don't get a link and there's no error. emailSent := err == nil && url == nil // If email is not sent a reset link will be generated var resetLink string if url != nil { resetLink = url.String() } return schema.ResendEmailInvitationResponse{ EmailSent: emailSent, ResetPasswordLink: resetLink, }, nil }
func NewClient(clientID, clientSecret, discovery, redirectURL string, tlsConfig *tls.Config) (*Client, *ClientCredsTokenManager) { cc := oidc.ClientCredentials{ ID: clientID, Secret: clientSecret, } httpClient := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}} var cfg oidc.ProviderConfig var err error for { cfg, err = oidc.FetchProviderConfig(httpClient, discovery) if err == nil { break } sleep := 3 * time.Second log.Errorf("Failed fetching provider config, trying again in %v: %v", sleep, err) time.Sleep(sleep) } log.Infof("Fetched provider config from %s", discovery) ccfg := oidc.ClientConfig{ HTTPClient: httpClient, ProviderConfig: cfg, Credentials: cc, RedirectURL: redirectURL, } client, err := NewOIDCClient(ccfg) if err != nil { log.Fatalf("Unable to create Client: %v", err) } client.SyncProviderConfig(discovery) tm := NewClientCredsTokenManager(client, discovery) tm.Run() tm.WaitUntilInitialSync() return client, tm }
func (r *SessionKeyRepo) purge() error { qt := r.quote(sessionKeyTableName) q := fmt.Sprintf("DELETE FROM %s WHERE stale = $1 OR expires_at < $2", qt) res, err := r.executor(nil).Exec(q, true, r.clock.Now().Unix()) if err != nil { return err } d := "unknown # of" if n, err := res.RowsAffected(); err == nil { if n == 0 { return nil } d = fmt.Sprintf("%d", n) } log.Infof("Deleted %s stale row(s) from %s table", d, sessionKeyTableName) return nil }
func (r *SessionRepo) purge() error { qt := pq.QuoteIdentifier(sessionTableName) q := fmt.Sprintf("DELETE FROM %s WHERE expires_at < $1 OR state = $2", qt) res, err := r.dbMap.Exec(q, r.clock.Now().Unix(), string(session.SessionStateDead)) if err != nil { return err } d := "unknown # of" if n, err := res.RowsAffected(); err == nil { if n == 0 { return nil } d = fmt.Sprintf("%d", n) } log.Infof("Deleted %s stale row(s) from %s table", d, sessionTableName) return nil }
func (c *LDAPConnector) Sync() chan struct{} { stop := make(chan struct{}) go func() { for { select { case <-time.After(c.idp.ldapPool.PoolCheckTimer): alive, killed := c.idp.ldapPool.CheckConnections() if alive > 0 { log.Infof("Connector ID=%v idle_conns=%v", c.id, alive) } if killed > 0 { log.Warningf("Connector ID=%v closed %v dead connections.", c.id, killed) } case <-stop: return } } }() return stop }
func (m *otsimoEmailer) SendMail(from, subject, event, data string, to ...string) error { email := &pb.Email{ FromEmail: from, Subject: subject, DataJson: data, ToEmail: to, } mes := &pb.Message{ Event: event, Targets: pb.NewTargets(pb.NewEmailTarget(email)), } if m.fake { log.Infof("email.go: email sent: %v", mes) return nil } _, err := m.client.SendMessage(context.Background(), mes) if err != nil { log.Errorf("email.go: sending email error: %v", err) } return err }
func (g *grpcServer) Register(ctx context.Context, in *pb.RegisterRequest) (*pb.RegisterResponse, error) { jwtClient, err := getJWTToken(ctx) if err != nil { log.Errorf("grpc.go: getJWTToken error %v", err) return nil, err } clientId, md, err := g.authToken(jwtClient) if err != nil { log.Errorf("grpc.go: authToken failed error=%v", err) return nil, err } id, err := registerFromLocalConnector(g.server.UserManager, g.localConnectorID, in.Email, in.Password) if err != nil { return nil, err } //send email g.server.UserEmailer.SendEmailVerification(id, clientId, md.RedirectURIs[0]) now := time.Now() jwt, rt, err := g.Token(id, clientId, now, now.Add(session.DefaultSessionValidityWindow)) if err != nil { return nil, err } log.Infof("grpc.go: token sent: userID=%s email:%s", id, in.Email) return &pb.RegisterResponse{ UserId: id, Token: &pb.Token{ AccessToken: jwt.Encode(), TokenType: "bearer", RefreshToken: rt, }, }, nil }
func (g *grpcServer) Login(ctx context.Context, in *pb.LoginRequest) (*pb.Token, error) { jwtClient, err := getJWTToken(ctx) if err != nil { log.Errorf("grpc.go: getJWTToken error %v", err) return nil, err } clientId, _, err := g.authToken(jwtClient) if err != nil { log.Errorf("grpc.go: authToken failed error=%v", err) return nil, err } email, password, ok := parseBasicAuth(in.BasicAuth) if !ok { return nil, errors.New("failed to parse basic auth") } i, err := g.idp.Identity(email, password) if err != nil { return nil, err } now := time.Now() jwt, rt, err := g.Token(i.ID, clientId, now, now.Add(session.DefaultSessionValidityWindow)) if err != nil { return nil, err } log.Infof("grpc.go: token sent: userID=%s email:%s", i.ID, email) return &pb.Token{ AccessToken: jwt.Encode(), TokenType: "bearer", RefreshToken: rt, }, nil }
func ServeGrpc(cfg *server.ServerConfig, srv *server.Server, grpcUrl string, certFile, keyFile string, tf repo.TransactionFactory) { var opts []grpc.ServerOption if certFile != "" && keyFile != "" { creds, err := credentials.NewServerTLSFromFile(certFile, keyFile) if err != nil { log.Fatalf("grpc.go: Failed to generate credentials %v", err) } opts = []grpc.ServerOption{grpc.Creds(creds)} } s := grpc.NewServer(opts...) rpcSrv := &grpcServer{ server: srv, idp: &connector.LocalIdentityProvider{ UserRepo: srv.UserRepo, PasswordInfoRepo: srv.PasswordInfoRepo, }, begin: tf, } for _, c := range srv.Connectors { if cc, ok := c.(*connector.LocalConnector); ok { rpcSrv.localConnectorID = cc.ID() break } } grpclog.SetLogger(golog.New(os.Stdout, "", 0)) pb.RegisterDexServiceServer(s, rpcSrv) lis, err := net.Listen("tcp", grpcUrl) if err != nil { log.Fatalf("grpc.go: failed to listen: %v", err) } log.Infof("grpc: Grpc server starting on %s", grpcUrl) s.Serve(lis) }
func (u *UsersAPI) ListUsers(creds Creds, maxResults int, nextPageToken string) ([]*schema.User, string, error) { log.Infof("userAPI: ListUsers") if !u.Authorize(creds) { return nil, "", ErrorUnauthorized } if maxResults > maxUsersPerPage { return nil, "", ErrorMaxResultsTooHigh } users, tok, err := u.manager.List(user.UserFilter{}, maxResults, nextPageToken) if err != nil { return nil, "", mapError(err) } list := []*schema.User{} for _, usr := range users { schemaUsr := userToSchemaUser(usr) list = append(list, &schemaUsr) } return list, tok, nil }
func main() { fs := flag.NewFlagSet("oidc-app", flag.ExitOnError) listen := fs.String("listen", "http://127.0.0.1:5555", "") redirectURL := fs.String("redirect-url", "http://127.0.0.1:5555/callback", "") clientID := fs.String("client-id", "example-app", "") clientSecret := fs.String("client-secret", "ZXhhbXBsZS1hcHAtc2VjcmV0", "") caFile := fs.String("trusted-ca-file", "", "the TLS CA file, if empty then the host's root CA will be used") certFile := fs.String("tls-cert-file", "", "the TLS cert file. If empty, the app will listen on HTTP") keyFile := fs.String("tls-key-file", "", "the TLS key file. If empty, the app will listen on HTTP") discovery := fs.String("discovery", "http://127.0.0.1:5556", "") logDebug := fs.Bool("log-debug", false, "log debug-level information") logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps") if err := fs.Parse(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if err := pflag.SetFlagsFromEnv(fs, "EXAMPLE_APP"); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if *logDebug { log.EnableDebug() } if *logTimestamps { log.EnableTimestamps() } if *clientID == "" { log.Fatal("--client-id must be set") } if *clientSecret == "" { log.Fatal("--client-secret must be set") } l, err := url.Parse(*listen) if err != nil { log.Fatalf("Unable to use --listen flag: %v", err) } _, p, err := net.SplitHostPort(l.Host) if err != nil { log.Fatalf("Unable to parse host from --listen flag: %v", err) } redirectURLParsed, err := url.Parse(*redirectURL) if err != nil { log.Fatalf("Unable to parse url from --redirect-url flag: %v", err) } useTLS := *keyFile != "" && *certFile != "" if useTLS && (redirectURLParsed.Scheme != "https" || l.Scheme != "https") { log.Fatalf(`TLS Cert File and Key File were provided. Ensure listen and redirect URLs are using the "https://" scheme.`) } cc := oidc.ClientCredentials{ ID: *clientID, Secret: *clientSecret, } var tlsConfig tls.Config if *caFile != "" { roots := x509.NewCertPool() pemBlock, err := ioutil.ReadFile(*caFile) if err != nil { log.Fatalf("Unable to read ca file: %v", err) } roots.AppendCertsFromPEM(pemBlock) tlsConfig.RootCAs = roots } httpClient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tlsConfig}} var cfg oidc.ProviderConfig for { cfg, err = oidc.FetchProviderConfig(httpClient, *discovery) if err == nil { break } sleep := 3 * time.Second log.Errorf("Failed fetching provider config, trying again in %v: %v", sleep, err) time.Sleep(sleep) } log.Infof("Fetched provider config from %s: %#v", *discovery, cfg) ccfg := oidc.ClientConfig{ HTTPClient: httpClient, ProviderConfig: cfg, Credentials: cc, RedirectURL: *redirectURL, Scope: append(oidc.DefaultScope, "offline_access"), } client, err := oidc.NewClient(ccfg) if err != nil { log.Fatalf("Unable to create Client: %v", err) } client.SyncProviderConfig(*discovery) hdlr := NewClientHandler(client, *discovery, *redirectURLParsed) httpsrv := &http.Server{ Addr: fmt.Sprintf(":%s", p), Handler: hdlr, } indexBytes, err := Asset("data/index.html") if err != nil { log.Fatalf("could not load template: %q", err) } indexTemplate = template.Must(template.New("root").Parse(string(indexBytes))) log.Infof("Binding to %s...", httpsrv.Addr) if useTLS { log.Info("Key and cert file provided. Using TLS") log.Fatal(httpsrv.ListenAndServeTLS(*certFile, *keyFile)) } else { log.Fatal(httpsrv.ListenAndServe()) } }
func (s *Server) CodeToken(creds oidc.ClientCredentials, sessionKey string) (*jose.JWT, string, error) { ok, err := s.ClientManager.Authenticate(creds) if err != nil { log.Errorf("Failed fetching client %s from repo: %v", creds.ID, err) return nil, "", oauth2.NewError(oauth2.ErrorServerError) } if !ok { log.Errorf("Failed to Authenticate client %s", creds.ID) return nil, "", oauth2.NewError(oauth2.ErrorInvalidClient) } sessionID, err := s.SessionManager.ExchangeKey(sessionKey) if err != nil { return nil, "", oauth2.NewError(oauth2.ErrorInvalidGrant) } ses, err := s.SessionManager.Kill(sessionID) if err != nil { return nil, "", oauth2.NewError(oauth2.ErrorInvalidRequest) } if ses.ClientID != creds.ID { return nil, "", oauth2.NewError(oauth2.ErrorInvalidGrant) } signer, err := s.KeyManager.Signer() if err != nil { log.Errorf("Failed to generate ID token: %v", err) return nil, "", oauth2.NewError(oauth2.ErrorServerError) } user, err := s.UserRepo.Get(nil, ses.UserID) if err != nil { log.Errorf("Failed to fetch user %q from repo: %v: ", ses.UserID, err) return nil, "", oauth2.NewError(oauth2.ErrorServerError) } claims := ses.Claims(s.IssuerURL.String()) user.AddToClaims(claims) s.addClaimsFromScope(claims, ses.Scope, ses.ClientID) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { log.Errorf("Failed to generate ID token: %v", err) return nil, "", oauth2.NewError(oauth2.ErrorServerError) } // Generate refresh token when 'scope' contains 'offline_access'. var refreshToken string for _, scope := range ses.Scope { if scope == "offline_access" { log.Infof("Session %s requests offline access, will generate refresh token", sessionID) refreshToken, err = s.RefreshTokenRepo.Create(ses.UserID, creds.ID, ses.Scope) switch err { case nil: break default: log.Errorf("Failed to generate refresh token: %v", err) return nil, "", oauth2.NewError(oauth2.ErrorServerError) } break } } log.Infof("Session %s token sent: clientID=%s", sessionID, creds.ID) return jwt, refreshToken, nil }
func (s *Server) Login(ident oidc.Identity, key string) (string, error) { sessionID, err := s.SessionManager.ExchangeKey(key) if err != nil { return "", err } ses, err := s.SessionManager.AttachRemoteIdentity(sessionID, ident) if err != nil { return "", err } log.Infof("Session %s remote identity attached: clientID=%s identity=%#v", sessionID, ses.ClientID, ident) if ses.Register { code, err := s.SessionManager.NewSessionKey(sessionID) if err != nil { return "", err } ru := s.absURL(httpPathRegister) q := ru.Query() q.Set("code", code) q.Set("state", ses.ClientState) ru.RawQuery = q.Encode() return ru.String(), nil } remoteIdentity := user.RemoteIdentity{ConnectorID: ses.ConnectorID, ID: ses.Identity.ID} // Get the connector used to log the user in. var conn connector.Connector for _, c := range s.Connectors { if c.ID() == ses.ConnectorID { conn = c break } } if conn == nil { return "", fmt.Errorf("session contained invalid connector ID (%s)", ses.ConnectorID) } usr, err := s.UserRepo.GetByRemoteIdentity(nil, remoteIdentity) if err == user.ErrorNotFound { if ses.Identity.Email == "" { // User doesn't have an existing account. Ask them to register. u := newLoginURLFromSession(s.IssuerURL, ses, true, []string{ses.ConnectorID}, "register-maybe") return u.String(), nil } // Does the user have an existing account with a different connector? if connID, err := getConnectorForUserByEmail(s.UserRepo, ses.Identity.Email); err == nil { // Ask user to sign in through existing account. u := newLoginURLFromSession(s.IssuerURL, ses, false, []string{connID}, "wrong-connector") return u.String(), nil } // RegisterOnFirstLogin doesn't work for the local connector tryToRegister := s.RegisterOnFirstLogin && (ses.ConnectorID != s.localConnectorID) if !tryToRegister { // User doesn't have an existing account. Ask them to register. u := newLoginURLFromSession(s.IssuerURL, ses, true, []string{ses.ConnectorID}, "register-maybe") return u.String(), nil } // First time logging in through a remote connector. Attempt to register. emailVerified := conn.TrustedEmailProvider() usrID, err := s.UserManager.RegisterWithRemoteIdentity(ses.Identity.Email, emailVerified, remoteIdentity) if err != nil { return "", fmt.Errorf("failed to register user: %v", err) } usr, err = s.UserManager.Get(usrID) if err != nil { return "", fmt.Errorf("getting created user: %v", err) } } else if err != nil { return "", fmt.Errorf("getting user: %v", err) } if usr.Disabled { log.Errorf("user %s disabled", ses.Identity.Email) return "", user.ErrorNotFound } ses, err = s.SessionManager.AttachUser(sessionID, usr.ID) if err != nil { return "", fmt.Errorf("attaching user to session: %v", err) } log.Infof("Session %s user identified: clientID=%s user=%#v", sessionID, ses.ClientID, usr) code, err := s.SessionManager.NewSessionKey(sessionID) if err != nil { return "", fmt.Errorf("creating new session key: %v", err) } ru := ses.RedirectURL if ru.String() == client.OOBRedirectURI { ru = s.absURL(httpPathOOB) } q := ru.Query() q.Set("code", code) q.Set("state", ses.ClientState) ru.RawQuery = q.Encode() return ru.String(), nil }