Skip to content
Snippets Groups Projects
api_test.go 7.26 KiB
Newer Older
  • Learn to ignore specific revisions
  • package server
    
    	"github.com/coreos/dex/server/internal"
    	"github.com/coreos/dex/storage"
    
    	"github.com/coreos/dex/storage/memory"
    
    	"github.com/sirupsen/logrus"
    
    // apiClient is a test gRPC client. When constructed, it runs a server in
    // the background to exercise the serialization and network configuration
    // instead of just this package's server implementation.
    type apiClient struct {
    	// Embedded gRPC client to talk to the server.
    	api.DexClient
    	// Close releases resources associated with this client, includuing shutting
    	// down the background server.
    	Close func()
    }
    
    // newAPI constructs a gRCP client connected to a backing server.
    func newAPI(s storage.Storage, logger logrus.FieldLogger, t *testing.T) *apiClient {
    	l, err := net.Listen("tcp", "127.0.0.1:0")
    	if err != nil {
    		t.Fatal(err)
    	}
    
    	serv := grpc.NewServer()
    	api.RegisterDexServer(serv, NewAPI(s, logger))
    	go serv.Serve(l)
    
    	// Dial will retry automatically if the serv.Serve() goroutine
    	// hasn't started yet.
    	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
    	if err != nil {
    		t.Fatal(err)
    	}
    
    	return &apiClient{
    		DexClient: api.NewDexClient(conn),
    		Close: func() {
    			conn.Close()
    			serv.Stop()
    			l.Close()
    		},
    	}
    }
    
    
    // Attempts to create, update and delete a test Password
    func TestPassword(t *testing.T) {
    
    	logger := &logrus.Logger{
    		Out:       os.Stderr,
    		Formatter: &logrus.TextFormatter{DisableColors: true},
    		Level:     logrus.DebugLevel,
    	}
    
    	s := memory.New(logger)
    
    	client := newAPI(s, logger, t)
    	defer client.Close()
    
    
    	ctx := context.Background()
    	p := api.Password{
    		Email: "test@example.com",
    		// bcrypt hash of the value "test1" with cost 10
    		Hash:     []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"),
    		Username: "test",
    		UserId:   "test123",
    	}
    
    	createReq := api.CreatePasswordReq{
    		Password: &p,
    	}
    
    
    	if resp, err := client.CreatePassword(ctx, &createReq); err != nil || resp.AlreadyExists {
    
    		if resp.AlreadyExists {
    			t.Fatalf("Unable to create password since %s already exists", createReq.Password.Email)
    		}
    
    		t.Fatalf("Unable to create password: %v", err)
    	}
    
    
    	// Attempt to create a password that already exists.
    
    	if resp, _ := client.CreatePassword(ctx, &createReq); !resp.AlreadyExists {
    
    		t.Fatalf("Created password %s twice", createReq.Password.Email)
    	}
    
    
    	updateReq := api.UpdatePasswordReq{
    		Email:       "test@example.com",
    		NewUsername: "test1",
    	}
    
    
    	if _, err := client.UpdatePassword(ctx, &updateReq); err != nil {
    
    		t.Fatalf("Unable to update password: %v", err)
    	}
    
    	pass, err := s.GetPassword(updateReq.Email)
    	if err != nil {
    		t.Fatalf("Unable to retrieve password: %v", err)
    	}
    
    	if pass.Username != updateReq.NewUsername {
    		t.Fatalf("UpdatePassword failed. Expected username %s retrieved %s", updateReq.NewUsername, pass.Username)
    	}
    
    	deleteReq := api.DeletePasswordReq{
    		Email: "test@example.com",
    	}
    
    
    	if _, err := client.DeletePassword(ctx, &deleteReq); err != nil {
    
    		t.Fatalf("Unable to delete password: %v", err)
    	}
    
    }
    
    // Ensures checkCost returns expected values
    func TestCheckCost(t *testing.T) {
    	logger := &logrus.Logger{
    		Out:       os.Stderr,
    		Formatter: &logrus.TextFormatter{DisableColors: true},
    		Level:     logrus.DebugLevel,
    	}
    
    	s := memory.New(logger)
    	client := newAPI(s, logger, t)
    	defer client.Close()
    
    	tests := []struct {
    
    		name      string
    		inputHash []byte
    
    		wantErr bool
    
    			// bcrypt hash of the value "test1" with cost 12 (default)
    			inputHash: []byte("$2a$12$M2Ot95Qty1MuQdubh1acWOiYadJDzeVg3ve4n5b.dgcgPdjCseKx2"),
    
    		},
    		{
    			name:      "invalid hash",
    			inputHash: []byte(""),
    			wantErr:   true,
    		},
    		{
    			name: "cost below default",
    			// bcrypt hash of the value "test1" with cost 4
    			inputHash: []byte("$2a$04$8bSTbuVCLpKzaqB3BmgI7edDigG5tIQKkjYUu/mEO9gQgIkw9m7eG"),
    			wantErr:   true,
    		},
    		{
    			name: "cost above recommendation",
    
    			// bcrypt hash of the value "test1" with cost 17
    			inputHash: []byte("$2a$17$tWuZkTxtSmRyWZAGWVHQE.7npdl.TgP8adjzLJD.SyjpFznKBftPe"),
    			wantErr:   true,
    
    		if err := checkCost(tc.inputHash); err != nil {
    
    			if !tc.wantErr {
    				t.Errorf("%s: %s", tc.name, err)
    			}
    			continue
    		}
    
    		if tc.wantErr {
    			t.Errorf("%s: expected err", tc.name)
    			continue
    		}
    	}
    }
    
    
    // Attempts to list and revoke an exisiting refresh token.
    func TestRefreshToken(t *testing.T) {
    	logger := &logrus.Logger{
    		Out:       os.Stderr,
    		Formatter: &logrus.TextFormatter{DisableColors: true},
    		Level:     logrus.DebugLevel,
    	}
    
    	s := memory.New(logger)
    
    	client := newAPI(s, logger, t)
    	defer client.Close()
    
    
    	ctx := context.Background()
    
    	// Creating a storage with an existing refresh token and offline session for the user.
    	id := storage.NewID()
    	r := storage.RefreshToken{
    		ID:          id,
    		Token:       "bar",
    		Nonce:       "foo",
    		ClientID:    "client_id",
    		ConnectorID: "client_secret",
    		Scopes:      []string{"openid", "email", "profile"},
    		CreatedAt:   time.Now().UTC().Round(time.Millisecond),
    		LastUsed:    time.Now().UTC().Round(time.Millisecond),
    		Claims: storage.Claims{
    			UserID:        "1",
    			Username:      "jane",
    			Email:         "jane.doe@example.com",
    			EmailVerified: true,
    			Groups:        []string{"a", "b"},
    		},
    		ConnectorData: []byte(`{"some":"data"}`),
    	}
    
    	if err := s.CreateRefresh(r); err != nil {
    		t.Fatalf("create refresh token: %v", err)
    	}
    
    	tokenRef := storage.RefreshTokenRef{
    		ID:        r.ID,
    		ClientID:  r.ClientID,
    		CreatedAt: r.CreatedAt,
    		LastUsed:  r.LastUsed,
    	}
    
    	session := storage.OfflineSessions{
    		UserID:  r.Claims.UserID,
    		ConnID:  r.ConnectorID,
    		Refresh: make(map[string]*storage.RefreshTokenRef),
    	}
    	session.Refresh[tokenRef.ClientID] = &tokenRef
    
    	if err := s.CreateOfflineSessions(session); err != nil {
    		t.Fatalf("create offline session: %v", err)
    	}
    
    	subjectString, err := internal.Marshal(&internal.IDTokenSubject{
    		UserId: r.Claims.UserID,
    		ConnId: r.ConnectorID,
    	})
    	if err != nil {
    		t.Errorf("failed to marshal offline session ID: %v", err)
    	}
    
    	//Testing the api.
    	listReq := api.ListRefreshReq{
    		UserId: subjectString,
    	}
    
    
    	listResp, err := client.ListRefresh(ctx, &listReq)
    
    		t.Fatalf("Unable to list refresh tokens for user: %v", err)
    	}
    
    
    	for _, tok := range listResp.RefreshTokens {
    		if tok.CreatedAt != r.CreatedAt.Unix() {
    			t.Errorf("Expected CreatedAt timestamp %v, got %v", r.CreatedAt.Unix(), tok.CreatedAt)
    		}
    
    		if tok.LastUsed != r.LastUsed.Unix() {
    			t.Errorf("Expected LastUsed timestamp %v, got %v", r.LastUsed.Unix(), tok.LastUsed)
    		}
    	}
    
    
    	revokeReq := api.RevokeRefreshReq{
    		UserId:   subjectString,
    		ClientId: r.ClientID,
    	}
    
    
    	resp, err := client.RevokeRefresh(ctx, &revokeReq)
    
    		t.Fatalf("Unable to revoke refresh tokens for user: %v", err)
    	}
    
    	if resp.NotFound {
    		t.Errorf("refresh token session wasn't found")
    	}
    
    	// Try to delete again.
    	//
    	// See https://github.com/coreos/dex/issues/1055
    	resp, err = client.RevokeRefresh(ctx, &revokeReq)
    	if err != nil {
    		t.Fatalf("Unable to revoke refresh tokens for user: %v", err)
    	}
    	if !resp.NotFound {
    		t.Errorf("refresh token session was found")
    	}
    
    	if resp, _ := client.ListRefresh(ctx, &listReq); len(resp.RefreshTokens) != 0 {
    
    		t.Fatalf("Refresh token returned inspite of revoking it.")
    	}
    }