github.com/letsencrypt/boulder@v0.20251208.0/sfe/sfe_test.go (about) 1 package sfe 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "net/http/httptest" 8 "net/url" 9 "testing" 10 "time" 11 12 "github.com/jmhodges/clock" 13 "google.golang.org/grpc" 14 15 "github.com/letsencrypt/boulder/cmd" 16 "github.com/letsencrypt/boulder/features" 17 blog "github.com/letsencrypt/boulder/log" 18 "github.com/letsencrypt/boulder/metrics" 19 "github.com/letsencrypt/boulder/mocks" 20 "github.com/letsencrypt/boulder/must" 21 "github.com/letsencrypt/boulder/ratelimits" 22 "github.com/letsencrypt/boulder/test" 23 "github.com/letsencrypt/boulder/unpause" 24 25 rapb "github.com/letsencrypt/boulder/ra/proto" 26 ) 27 28 type MockRegistrationAuthority struct { 29 rapb.RegistrationAuthorityClient 30 } 31 32 func (ra *MockRegistrationAuthority) UnpauseAccount(context.Context, *rapb.UnpauseAccountRequest, ...grpc.CallOption) (*rapb.UnpauseAccountResponse, error) { 33 return &rapb.UnpauseAccountResponse{}, nil 34 } 35 36 func mustParseURL(s string) *url.URL { 37 return must.Do(url.Parse(s)) 38 } 39 40 func setupSFE(t *testing.T) (SelfServiceFrontEndImpl, clock.FakeClock) { 41 features.Reset() 42 43 fc := clock.NewFake() 44 // Set to some non-zero time. 45 fc.Set(time.Date(2020, 10, 10, 0, 0, 0, 0, time.UTC)) 46 47 stats := metrics.NoopRegisterer 48 logger := blog.NewMock() 49 50 mockSA := mocks.NewStorageAuthorityReadOnly(fc) 51 52 hmacKey := cmd.HMACKeyConfig{KeyFile: "../test/secrets/sfe_unpause_key"} 53 key, err := hmacKey.Load() 54 test.AssertNotError(t, err, "Unable to load HMAC key") 55 56 limiter, err := ratelimits.NewLimiter(fc, ratelimits.NewInmemSource(), stats) 57 test.AssertNotError(t, err, "making limiter") 58 txnBuilder, err := ratelimits.NewTransactionBuilderFromFiles("../test/config-next/sfe-ratelimit-defaults.yml", "", stats, logger) 59 test.AssertNotError(t, err, "making transaction composer") 60 61 sfe, err := NewSelfServiceFrontEndImpl( 62 stats, 63 fc, 64 logger, 65 10*time.Second, 66 &MockRegistrationAuthority{}, 67 mockSA, 68 nil, 69 key, 70 nil, 71 limiter, 72 txnBuilder, 73 false, 74 ) 75 test.AssertNotError(t, err, "Unable to create SFE") 76 77 return sfe, fc 78 } 79 80 func TestIndexPath(t *testing.T) { 81 t.Parallel() 82 sfe, _ := setupSFE(t) 83 responseWriter := httptest.NewRecorder() 84 sfe.Index(responseWriter, &http.Request{ 85 Method: "GET", 86 URL: mustParseURL("/"), 87 }) 88 89 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 90 test.AssertContains(t, responseWriter.Body.String(), "<title>Let's Encrypt - Portal</title>") 91 } 92 93 func TestBuildIDPath(t *testing.T) { 94 t.Parallel() 95 sfe, _ := setupSFE(t) 96 responseWriter := httptest.NewRecorder() 97 sfe.BuildID(responseWriter, &http.Request{ 98 Method: "GET", 99 URL: mustParseURL("/build"), 100 }) 101 102 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 103 test.AssertContains(t, responseWriter.Body.String(), "Boulder=(") 104 } 105 106 func TestUnpausePaths(t *testing.T) { 107 t.Parallel() 108 sfe, fc := setupSFE(t) 109 unpauseSigner, err := unpause.NewJWTSigner(cmd.HMACKeyConfig{KeyFile: "../test/secrets/sfe_unpause_key"}) 110 test.AssertNotError(t, err, "Should have been able to create JWT signer, but could not") 111 112 // GET with no JWT 113 responseWriter := httptest.NewRecorder() 114 sfe.UnpauseForm(responseWriter, &http.Request{ 115 Method: "GET", 116 URL: mustParseURL(unpause.GetForm), 117 }) 118 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 119 test.AssertContains(t, responseWriter.Body.String(), "Invalid unpause URL") 120 121 // GET with an invalid JWT 122 responseWriter = httptest.NewRecorder() 123 sfe.UnpauseForm(responseWriter, &http.Request{ 124 Method: "GET", 125 URL: mustParseURL(fmt.Sprintf(unpause.GetForm + "?jwt=x")), 126 }) 127 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 128 test.AssertContains(t, responseWriter.Body.String(), "Invalid unpause URL") 129 130 // GET with an expired JWT 131 expiredJWT, err := unpause.GenerateJWT(unpauseSigner, 1234567890, []string{"example.net"}, time.Hour, fc) 132 test.AssertNotError(t, err, "Should have been able to create JWT, but could not") 133 responseWriter = httptest.NewRecorder() 134 // Advance the clock by 337 hours to make the JWT expired. 135 fc.Add(time.Hour * 337) 136 sfe.UnpauseForm(responseWriter, &http.Request{ 137 Method: "GET", 138 URL: mustParseURL(unpause.GetForm + "?jwt=" + expiredJWT), 139 }) 140 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 141 test.AssertContains(t, responseWriter.Body.String(), "Expired unpause URL") 142 143 // GET with a valid JWT and a single identifier 144 validJWT, err := unpause.GenerateJWT(unpauseSigner, 1234567890, []string{"example.com"}, time.Hour, fc) 145 test.AssertNotError(t, err, "Should have been able to create JWT, but could not") 146 responseWriter = httptest.NewRecorder() 147 sfe.UnpauseForm(responseWriter, &http.Request{ 148 Method: "GET", 149 URL: mustParseURL(unpause.GetForm + "?jwt=" + validJWT), 150 }) 151 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 152 test.AssertContains(t, responseWriter.Body.String(), "Action required to unpause your account") 153 test.AssertContains(t, responseWriter.Body.String(), "example.com") 154 155 // GET with a valid JWT and multiple identifiers 156 validJWT, err = unpause.GenerateJWT(unpauseSigner, 1234567890, []string{"example.com", "example.net", "example.org"}, time.Hour, fc) 157 test.AssertNotError(t, err, "Should have been able to create JWT, but could not") 158 responseWriter = httptest.NewRecorder() 159 sfe.UnpauseForm(responseWriter, &http.Request{ 160 Method: "GET", 161 URL: mustParseURL(unpause.GetForm + "?jwt=" + validJWT), 162 }) 163 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 164 test.AssertContains(t, responseWriter.Body.String(), "Action required to unpause your account") 165 test.AssertContains(t, responseWriter.Body.String(), "example.com") 166 test.AssertContains(t, responseWriter.Body.String(), "example.net") 167 test.AssertContains(t, responseWriter.Body.String(), "example.org") 168 169 // POST with an expired JWT 170 responseWriter = httptest.NewRecorder() 171 sfe.UnpauseSubmit(responseWriter, &http.Request{ 172 Method: "POST", 173 URL: mustParseURL(unpausePostForm + "?jwt=" + expiredJWT), 174 }) 175 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 176 test.AssertContains(t, responseWriter.Body.String(), "Expired unpause URL") 177 178 // POST with no JWT 179 responseWriter = httptest.NewRecorder() 180 sfe.UnpauseSubmit(responseWriter, &http.Request{ 181 Method: "POST", 182 URL: mustParseURL(unpausePostForm), 183 }) 184 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 185 test.AssertContains(t, responseWriter.Body.String(), "Invalid unpause URL") 186 187 // POST with an invalid JWT, missing one of the three parts 188 responseWriter = httptest.NewRecorder() 189 sfe.UnpauseSubmit(responseWriter, &http.Request{ 190 Method: "POST", 191 URL: mustParseURL(unpausePostForm + "?jwt=x.x"), 192 }) 193 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 194 test.AssertContains(t, responseWriter.Body.String(), "Invalid unpause URL") 195 196 // POST with an invalid JWT, all parts present but missing some characters 197 responseWriter = httptest.NewRecorder() 198 sfe.UnpauseSubmit(responseWriter, &http.Request{ 199 Method: "POST", 200 URL: mustParseURL(unpausePostForm + "?jwt=x.x.x"), 201 }) 202 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 203 test.AssertContains(t, responseWriter.Body.String(), "Invalid unpause URL") 204 205 // POST with a valid JWT redirects to a success page 206 responseWriter = httptest.NewRecorder() 207 sfe.UnpauseSubmit(responseWriter, &http.Request{ 208 Method: "POST", 209 URL: mustParseURL(unpausePostForm + "?jwt=" + validJWT), 210 }) 211 test.AssertEquals(t, responseWriter.Code, http.StatusFound) 212 test.AssertEquals(t, unpauseStatus+"?count=0", responseWriter.Result().Header.Get("Location")) 213 214 // Redirecting after a successful unpause POST displays the success page. 215 responseWriter = httptest.NewRecorder() 216 sfe.UnpauseStatus(responseWriter, &http.Request{ 217 Method: "GET", 218 URL: mustParseURL(unpauseStatus + "?count=1"), 219 }) 220 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 221 test.AssertContains(t, responseWriter.Body.String(), "Successfully unpaused all 1 identifier(s)") 222 223 // Redirecting after a successful unpause POST with a count of 0 displays 224 // the already unpaused page. 225 responseWriter = httptest.NewRecorder() 226 sfe.UnpauseStatus(responseWriter, &http.Request{ 227 Method: "GET", 228 URL: mustParseURL(unpauseStatus + "?count=0"), 229 }) 230 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 231 test.AssertContains(t, responseWriter.Body.String(), "Account already unpaused") 232 233 // Redirecting after a successful unpause POST with a count equal to the 234 // maximum number of identifiers displays the success with caveat page. 235 responseWriter = httptest.NewRecorder() 236 sfe.UnpauseStatus(responseWriter, &http.Request{ 237 Method: "GET", 238 URL: mustParseURL(unpauseStatus + "?count=" + fmt.Sprintf("%d", unpause.RequestLimit)), 239 }) 240 test.AssertEquals(t, responseWriter.Code, http.StatusOK) 241 test.AssertContains(t, responseWriter.Body.String(), "Some identifiers were unpaused") 242 }