• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

box / box-java-sdk / #4076

12 Nov 2024 10:48AM UTC coverage: 71.771% (+0.03%) from 71.737%
#4076

Pull #1272

github

web-flow
Merge 8c67b7acb into 6ea70f79a
Pull Request #1272: feat: allow to modify the underlying client from the Box API connection subclasses

3 of 4 new or added lines in 4 files covered. (75.0%)

98 existing lines in 4 files now uncovered.

8052 of 11219 relevant lines covered (71.77%)

0.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.49
/src/main/java/com/box/sdk/BoxDeveloperEditionAPIConnection.java
1
package com.box.sdk;
2

3
import com.eclipsesource.json.Json;
4
import com.eclipsesource.json.JsonObject;
5
import java.net.MalformedURLException;
6
import java.net.URL;
7
import java.text.ParseException;
8
import java.text.SimpleDateFormat;
9
import java.util.Date;
10
import java.util.List;
11
import okhttp3.OkHttpClient;
12
import org.jose4j.jws.AlgorithmIdentifiers;
13
import org.jose4j.jws.JsonWebSignature;
14
import org.jose4j.jwt.JwtClaims;
15
import org.jose4j.jwt.NumericDate;
16
import org.jose4j.lang.JoseException;
17

18
/**
19
 * Represents an authenticated Box Developer Edition connection to the Box API.
20
 *
21
 * <p>This class handles everything for Box Developer Edition that isn't already handled by BoxAPIConnection.</p>
22
 */
23
public class BoxDeveloperEditionAPIConnection extends BoxAPIConnection {
1✔
24

25
    private static final String JWT_AUDIENCE = "https://api.box.com/oauth2/token";
26
    private static final String JWT_GRANT_TYPE =
27
        "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&client_id=%s&client_secret=%s&assertion=%s";
28
    private static final int DEFAULT_MAX_ENTRIES = 100;
29

30
    private final String entityID;
31
    private final DeveloperEditionEntityType entityType;
32
    private final EncryptionAlgorithm encryptionAlgorithm;
33
    private final String publicKeyID;
34
    private final String privateKey;
35
    private final String privateKeyPassword;
36
    private BackoffCounter backoffCounter;
37
    private final IAccessTokenCache accessTokenCache;
38
    private final IPrivateKeyDecryptor privateKeyDecryptor;
39

40
    /**
41
     * Constructs a new BoxDeveloperEditionAPIConnection leveraging an access token cache.
42
     *
43
     * @param entityId         enterprise ID or a user ID.
44
     * @param entityType       the type of entityId.
45
     * @param clientID         the client ID to use when exchanging the JWT assertion for an access token.
46
     * @param clientSecret     the client secret to use when exchanging the JWT assertion for an access token.
47
     * @param encryptionPref   the encryption preferences for signing the JWT.
48
     * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens)
49
     */
50
    public BoxDeveloperEditionAPIConnection(String entityId, DeveloperEditionEntityType entityType,
51
                                            String clientID, String clientSecret,
52
                                            JWTEncryptionPreferences encryptionPref,
53
                                            IAccessTokenCache accessTokenCache) {
54

55
        super(clientID, clientSecret);
1✔
56

57
        this.entityID = entityId;
1✔
58
        this.entityType = entityType;
1✔
59
        this.publicKeyID = encryptionPref.getPublicKeyID();
1✔
60
        this.privateKey = encryptionPref.getPrivateKey();
1✔
61
        this.privateKeyPassword = encryptionPref.getPrivateKeyPassword();
1✔
62
        this.encryptionAlgorithm = encryptionPref.getEncryptionAlgorithm();
1✔
63
        this.privateKeyDecryptor = encryptionPref.getPrivateKeyDecryptor();
1✔
64
        this.accessTokenCache = accessTokenCache;
1✔
65
        this.backoffCounter = new BackoffCounter(new Time());
1✔
66
    }
1✔
67

68
    /**
69
     * Constructs a new BoxDeveloperEditionAPIConnection.
70
     * Uses {@link InMemoryLRUAccessTokenCache} with a size of 100 to prevent unneeded
71
     * requests to Box for access tokens.
72
     *
73
     * @param entityId       enterprise ID or a user ID.
74
     * @param entityType     the type of entityId.
75
     * @param clientID       the client ID to use when exchanging the JWT assertion for an access token.
76
     * @param clientSecret   the client secret to use when exchanging the JWT assertion for an access token.
77
     * @param encryptionPref the encryption preferences for signing the JWT.
78
     */
79
    public BoxDeveloperEditionAPIConnection(
80
        String entityId,
81
        DeveloperEditionEntityType entityType,
82
        String clientID,
83
        String clientSecret,
84
        JWTEncryptionPreferences encryptionPref
85
    ) {
86

UNCOV
87
        this(
×
88
            entityId,
89
            entityType,
90
            clientID,
91
            clientSecret,
92
            encryptionPref,
93
            new InMemoryLRUAccessTokenCache(DEFAULT_MAX_ENTRIES)
94
        );
UNCOV
95
    }
×
96

97
    /**
98
     * Constructs a new BoxDeveloperEditionAPIConnection.
99
     *
100
     * @param entityId         enterprise ID or a user ID.
101
     * @param entityType       the type of entityId.
102
     * @param boxConfig        box configuration settings object
103
     * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens)
104
     */
105
    public BoxDeveloperEditionAPIConnection(String entityId, DeveloperEditionEntityType entityType,
106
                                            BoxConfig boxConfig, IAccessTokenCache accessTokenCache) {
107

108
        this(entityId, entityType, boxConfig.getClientId(), boxConfig.getClientSecret(),
×
109
            boxConfig.getJWTEncryptionPreferences(), accessTokenCache);
×
UNCOV
110
    }
×
111

112
    /**
113
     * {@inheritDoc}
114
     */
115
    @Override
116
    protected OkHttpClient.Builder modifyHttpClientBuilder(OkHttpClient.Builder httpClientBuilder) {
117
        return super.modifyHttpClientBuilder(httpClientBuilder);
1✔
118
    }
119

120
    /**
121
     * Creates a new Box Developer Edition connection with enterprise token leveraging an access token cache.
122
     *
123
     * @param enterpriseId     the enterprise ID to use for requesting access token.
124
     * @param clientId         the client ID to use when exchanging the JWT assertion for an access token.
125
     * @param clientSecret     the client secret to use when exchanging the JWT assertion for an access token.
126
     * @param encryptionPref   the encryption preferences for signing the JWT.
127
     * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens)
128
     * @return a new instance of BoxAPIConnection.
129
     */
130
    public static BoxDeveloperEditionAPIConnection getAppEnterpriseConnection(
131
        String enterpriseId,
132
        String clientId,
133
        String clientSecret,
134
        JWTEncryptionPreferences encryptionPref,
135
        IAccessTokenCache accessTokenCache
136
    ) {
137

UNCOV
138
        BoxDeveloperEditionAPIConnection connection = new BoxDeveloperEditionAPIConnection(enterpriseId,
×
139
            DeveloperEditionEntityType.ENTERPRISE, clientId, clientSecret, encryptionPref, accessTokenCache);
140

UNCOV
141
        connection.tryRestoreUsingAccessTokenCache();
×
142

UNCOV
143
        return connection;
×
144
    }
145

146
    /**
147
     * Creates a new Box Developer Edition connection with enterprise token.
148
     * Uses {@link InMemoryLRUAccessTokenCache} with a size of 100 to prevent unneeded
149
     * requests to Box for access tokens.
150
     *
151
     * @param enterpriseId   the enterprise ID to use for requesting access token.
152
     * @param clientId       the client ID to use when exchanging the JWT assertion for an access token.
153
     * @param clientSecret   the client secret to use when exchanging the JWT assertion for an access token.
154
     * @param encryptionPref the encryption preferences for signing the JWT.
155
     * @return a new instance of BoxAPIConnection.
156
     */
157
    public static BoxDeveloperEditionAPIConnection getAppEnterpriseConnection(
158
        String enterpriseId,
159
        String clientId,
160
        String clientSecret,
161
        JWTEncryptionPreferences encryptionPref
162
    ) {
163

UNCOV
164
        BoxDeveloperEditionAPIConnection connection = new BoxDeveloperEditionAPIConnection(
×
165
            enterpriseId,
166
            DeveloperEditionEntityType.ENTERPRISE,
167
            clientId,
168
            clientSecret,
169
            encryptionPref
170
        );
171

UNCOV
172
        connection.authenticate();
×
173

UNCOV
174
        return connection;
×
175
    }
176

177
    /**
178
     * Creates a new Box Developer Edition connection with enterprise token leveraging BoxConfig and access token cache.
179
     *
180
     * @param boxConfig        box configuration settings object
181
     * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens)
182
     * @return a new instance of BoxAPIConnection.
183
     */
184
    public static BoxDeveloperEditionAPIConnection getAppEnterpriseConnection(BoxConfig boxConfig,
185
                                                                              IAccessTokenCache accessTokenCache) {
186

UNCOV
187
        return getAppEnterpriseConnection(
×
UNCOV
188
            boxConfig.getEnterpriseId(),
×
UNCOV
189
            boxConfig.getClientId(),
×
UNCOV
190
            boxConfig.getClientSecret(),
×
UNCOV
191
            boxConfig.getJWTEncryptionPreferences(),
×
192
            accessTokenCache
193
        );
194
    }
195

196
    /**
197
     * Creates a new Box Developer Edition connection with enterprise token leveraging BoxConfig.
198
     * Uses {@link InMemoryLRUAccessTokenCache} with a size of 100 to prevent unneeded
199
     * requests to Box for access tokens.
200
     *
201
     * @param boxConfig box configuration settings object
202
     * @return a new instance of BoxAPIConnection.
203
     */
204
    public static BoxDeveloperEditionAPIConnection getAppEnterpriseConnection(BoxConfig boxConfig) {
205

UNCOV
206
        return getAppEnterpriseConnection(
×
UNCOV
207
            boxConfig.getEnterpriseId(),
×
UNCOV
208
            boxConfig.getClientId(),
×
UNCOV
209
            boxConfig.getClientSecret(),
×
UNCOV
210
            boxConfig.getJWTEncryptionPreferences()
×
211
        );
212
    }
213

214
    /**
215
     * Creates a new Box Developer Edition connection with App User or Managed User token.
216
     *
217
     * @param userId           the user ID to use for an App User.
218
     * @param clientId         the client ID to use when exchanging the JWT assertion for an access token.
219
     * @param clientSecret     the client secret to use when exchanging the JWT assertion for an access token.
220
     * @param encryptionPref   the encryption preferences for signing the JWT.
221
     * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens)
222
     * @return a new instance of BoxAPIConnection.
223
     */
224
    public static BoxDeveloperEditionAPIConnection getUserConnection(
225
        String userId,
226
        String clientId,
227
        String clientSecret,
228
        JWTEncryptionPreferences encryptionPref,
229
        IAccessTokenCache accessTokenCache
230
    ) {
231
        BoxDeveloperEditionAPIConnection connection = new BoxDeveloperEditionAPIConnection(
×
232
            userId,
233
            DeveloperEditionEntityType.USER,
234
            clientId,
235
            clientSecret,
236
            encryptionPref,
237
            accessTokenCache
238
        );
239

UNCOV
240
        connection.tryRestoreUsingAccessTokenCache();
×
241

UNCOV
242
        return connection;
×
243
    }
244

245
    /**
246
     * Creates a new Box Developer Edition connection with App User or Managed User token leveraging BoxConfig
247
     * and access token cache.
248
     *
249
     * @param userId           the user ID to use for an App User.
250
     * @param boxConfig        box configuration settings object
251
     * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens)
252
     * @return a new instance of BoxAPIConnection.
253
     */
254
    public static BoxDeveloperEditionAPIConnection getUserConnection(
255
        String userId,
256
        BoxConfig boxConfig,
257
        IAccessTokenCache accessTokenCache
258
    ) {
UNCOV
259
        return getUserConnection(
×
260
            userId,
UNCOV
261
            boxConfig.getClientId(),
×
UNCOV
262
            boxConfig.getClientSecret(),
×
UNCOV
263
            boxConfig.getJWTEncryptionPreferences(),
×
264
            accessTokenCache
265
        );
266
    }
267

268
    /**
269
     * Creates a new Box Developer Edition connection with App User or Managed User token.
270
     * Uses {@link InMemoryLRUAccessTokenCache} with a size of 100 to prevent unneeded
271
     * requests to Box for access tokens.
272
     *
273
     * @param userId    the user ID to use for an App User.
274
     * @param boxConfig box configuration settings object
275
     * @return a new instance of BoxAPIConnection.
276
     */
277
    public static BoxDeveloperEditionAPIConnection getUserConnection(String userId, BoxConfig boxConfig) {
UNCOV
278
        return getUserConnection(
×
279
            userId,
UNCOV
280
            boxConfig.getClientId(),
×
UNCOV
281
            boxConfig.getClientSecret(),
×
UNCOV
282
            boxConfig.getJWTEncryptionPreferences(),
×
283
            new InMemoryLRUAccessTokenCache(DEFAULT_MAX_ENTRIES));
284
    }
285

286
    /**
287
     * Disabling the non-Box Developer Edition authenticate method.
288
     *
289
     * @param authCode an auth code obtained from the first half of the OAuth process.
290
     */
291
    public void authenticate(String authCode) {
UNCOV
292
        throw new BoxAPIException("BoxDeveloperEditionAPIConnection does not allow authenticating with an auth code.");
×
293
    }
294

295
    /**
296
     * Authenticates the API connection for Box Developer Edition.
297
     */
298
    public void authenticate() {
299
        URL url;
300
        try {
301
            url = new URL(this.getTokenURL());
1✔
UNCOV
302
        } catch (MalformedURLException e) {
×
UNCOV
303
            assert false : "An invalid token URL indicates a bug in the SDK.";
×
UNCOV
304
            throw new RuntimeException("An invalid token URL indicates a bug in the SDK.", e);
×
305
        }
1✔
306

307
        this.backoffCounter.reset(this.getMaxRetryAttempts() + 1);
1✔
308
        NumericDate jwtTime = null;
1✔
309
        String jwtAssertion;
310
        String urlParameters;
311
        BoxAPIRequest request;
312
        String json = null;
1✔
313
        final BoxLogger logger = BoxLogger.defaultLogger();
1✔
314

315
        while (this.backoffCounter.getAttemptsRemaining() > 0) {
1✔
316
            // Reconstruct the JWT assertion, which regenerates the jti claim, with the new "current" time
317
            jwtAssertion = this.constructJWTAssertion(jwtTime);
1✔
318
            urlParameters = String.format(JWT_GRANT_TYPE, this.getClientID(), this.getClientSecret(), jwtAssertion);
1✔
319

320
            request = new BoxAPIRequest(this, url, "POST");
1✔
321
            request.shouldAuthenticate(false);
1✔
322
            request.setBody(urlParameters);
1✔
323

324
            try (BoxJSONResponse response = (BoxJSONResponse) request.sendWithoutRetry()) {
1✔
325
                // authentication uses form url encoded but response is JSON
326
                json = response.getJSON();
1✔
327
                break;
328
            } catch (BoxAPIException apiException) {
1✔
329
                long responseReceivedTime = System.currentTimeMillis();
1✔
330

331
                if (!this.backoffCounter.decrement()
1✔
332
                    || (!BoxAPIRequest.isRequestRetryable(apiException) && !isResponseRetryable(apiException))) {
1✔
333
                    throw apiException;
1✔
334
                }
335

336
                logger.warn(String.format(
1✔
337
                    "Retrying authentication request due to transient error status=%d body=%s",
338
                    apiException.getResponseCode(),
1✔
339
                    apiException.getResponse()
1✔
340
                ));
341

342
                try {
343
                    List<String> retryAfterHeader = apiException.getHeaders().get("Retry-After");
1✔
344
                    if (retryAfterHeader == null) {
1✔
345
                        this.backoffCounter.waitBackoff();
1✔
346
                    } else {
347
                        int retryAfterDelay = Integer.parseInt(retryAfterHeader.get(0)) * 1000;
1✔
348
                        this.backoffCounter.waitBackoff(retryAfterDelay);
1✔
349
                    }
UNCOV
350
                } catch (InterruptedException interruptedException) {
×
UNCOV
351
                    Thread.currentThread().interrupt();
×
UNCOV
352
                    throw apiException;
×
353
                }
1✔
354

355
                long endWaitTime = System.currentTimeMillis();
1✔
356
                long secondsSinceResponseReceived = (endWaitTime - responseReceivedTime) / 1000;
1✔
357

358
                try {
359
                    // Use the Date advertised by the Box server in the exception
360
                    // as the current time to synchronize clocks
361
                    jwtTime = this.getDateForJWTConstruction(apiException, secondsSinceResponseReceived);
1✔
UNCOV
362
                } catch (Exception e) {
×
UNCOV
363
                    throw apiException;
×
364
                }
1✔
365

366
            }
1✔
367
        }
368

369
        if (json == null) {
1✔
UNCOV
370
            throw new RuntimeException("Unable to read authentication response in SDK.");
×
371
        }
372

373
        JsonObject jsonObject = Json.parse(json).asObject();
1✔
374
        this.setAccessToken(jsonObject.get("access_token").asString());
1✔
375
        this.setLastRefresh(System.currentTimeMillis());
1✔
376
        this.setExpires(jsonObject.get("expires_in").asLong() * 1000);
1✔
377

378
        //if token cache is specified, save to cache
379
        if (this.accessTokenCache != null) {
1✔
UNCOV
380
            String key = this.getAccessTokenCacheKey();
×
UNCOV
381
            JsonObject accessTokenCacheInfo = new JsonObject()
×
UNCOV
382
                .add("accessToken", this.getAccessToken())
×
UNCOV
383
                .add("lastRefresh", this.getLastRefresh())
×
UNCOV
384
                .add("expires", this.getExpires());
×
385

UNCOV
386
            this.accessTokenCache.put(key, accessTokenCacheInfo.toString());
×
387
        }
388
    }
1✔
389

390
    private boolean isResponseRetryable(BoxAPIException apiException) {
391
        return BoxAPIRequest.isResponseRetryable(apiException.getResponseCode(), apiException)
1✔
392
            || isJtiNonUniqueError(apiException);
1✔
393
    }
394

395
    private boolean isJtiNonUniqueError(BoxAPIException apiException) {
396
        return apiException.getResponseCode() == 400
1✔
397
            && apiException.getResponse().contains("A unique 'jti' value is required");
1✔
398
    }
399

400
    private NumericDate getDateForJWTConstruction(BoxAPIException apiException, long secondsSinceResponseDateReceived) {
401
        NumericDate currentTime;
402
        List<String> responseDates = apiException.getHeaders().get("Date");
1✔
403

404
        if (responseDates != null) {
1✔
405
            String responseDate = responseDates.get(0);
1✔
406
            SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz");
1✔
407
            try {
408
                Date date = dateFormat.parse(responseDate);
1✔
409
                currentTime = NumericDate.fromMilliseconds(date.getTime());
1✔
410
                currentTime.addSeconds(secondsSinceResponseDateReceived);
1✔
UNCOV
411
            } catch (ParseException e) {
×
412
                currentTime = NumericDate.now();
×
413
            }
1✔
414
        } else {
1✔
415
            currentTime = NumericDate.now();
1✔
416
        }
417
        return currentTime;
1✔
418
    }
419

420
    void setBackoffCounter(BackoffCounter counter) {
UNCOV
421
        this.backoffCounter = counter;
×
UNCOV
422
    }
×
423

424
    /**
425
     * BoxDeveloperEditionAPIConnection can always refresh, but this method is required elsewhere.
426
     *
427
     * @return true always.
428
     */
429
    public boolean canRefresh() {
430
        return true;
1✔
431
    }
432

433
    /**
434
     * Refresh's this connection's access token using Box Developer Edition.
435
     *
436
     * @throws IllegalStateException if this connection's access token cannot be refreshed.
437
     */
438
    public void refresh() {
UNCOV
439
        this.getRefreshLock().writeLock().lock();
×
440

441
        try {
442
            this.authenticate();
×
UNCOV
443
        } catch (BoxAPIException e) {
×
UNCOV
444
            this.notifyError(e);
×
445
            this.getRefreshLock().writeLock().unlock();
×
446
            throw e;
×
UNCOV
447
        }
×
448

449
        this.notifyRefresh();
×
450
        this.getRefreshLock().writeLock().unlock();
×
451
    }
×
452

453
    private String getAccessTokenCacheKey() {
UNCOV
454
        return String.format("/%s/%s/%s/%s", this.getUserAgent(), this.getClientID(),
×
455
            this.entityType.toString(), this.entityID);
×
456
    }
457

458
    private void tryRestoreUsingAccessTokenCache() {
UNCOV
459
        if (this.accessTokenCache == null) {
×
460
            //no cache specified so force authentication
UNCOV
461
            this.authenticate();
×
462
        } else {
463
            String cachedTokenInfo = this.accessTokenCache.get(this.getAccessTokenCacheKey());
×
464
            if (cachedTokenInfo == null) {
×
465
                //not found; probably first time for this client config so authenticate; info will then be cached
466
                this.authenticate();
×
467
            } else {
468
                //pull access token cache info; authentication will occur as needed (if token is expired)
469
                JsonObject json = Json.parse(cachedTokenInfo).asObject();
×
UNCOV
470
                this.setAccessToken(json.get("accessToken").asString());
×
UNCOV
471
                this.setLastRefresh(json.get("lastRefresh").asLong());
×
UNCOV
472
                this.setExpires(json.get("expires").asLong());
×
473
            }
474
        }
UNCOV
475
    }
×
476

477
    private String constructJWTAssertion(NumericDate now) {
478
        JwtClaims claims = new JwtClaims();
1✔
479
        claims.setIssuer(this.getClientID());
1✔
480
        claims.setAudience(JWT_AUDIENCE);
1✔
481
        if (now == null) {
1✔
482
            claims.setExpirationTimeMinutesInTheFuture(0.5f);
1✔
483
        } else {
484
            now.addSeconds(30L);
1✔
485
            claims.setExpirationTime(now);
1✔
486
        }
487
        claims.setSubject(this.entityID);
1✔
488
        claims.setClaim("box_sub_type", this.entityType.toString());
1✔
489
        claims.setGeneratedJwtId(64);
1✔
490

491
        JsonWebSignature jws = new JsonWebSignature();
1✔
492
        jws.setPayload(claims.toJson());
1✔
493
        jws.setKey(this.privateKeyDecryptor.decryptPrivateKey(this.privateKey, this.privateKeyPassword));
1✔
494
        jws.setAlgorithmHeaderValue(this.getAlgorithmIdentifier());
1✔
495
        jws.setHeader("typ", "JWT");
1✔
496
        if ((this.publicKeyID != null) && !this.publicKeyID.isEmpty()) {
1✔
497
            jws.setHeader("kid", this.publicKeyID);
1✔
498
        }
499

500
        String assertion;
501

502
        try {
503
            assertion = jws.getCompactSerialization();
1✔
UNCOV
504
        } catch (JoseException e) {
×
UNCOV
505
            throw new BoxAPIException("Error serializing JSON Web Token assertion.", e);
×
506
        }
1✔
507

508
        return assertion;
1✔
509
    }
510

511
    private String getAlgorithmIdentifier() {
512
        String algorithmId = AlgorithmIdentifiers.RSA_USING_SHA256;
1✔
513
        switch (this.encryptionAlgorithm) {
1✔
514
            case RSA_SHA_384:
UNCOV
515
                algorithmId = AlgorithmIdentifiers.RSA_USING_SHA384;
×
UNCOV
516
                break;
×
517
            case RSA_SHA_512:
UNCOV
518
                algorithmId = AlgorithmIdentifiers.RSA_USING_SHA512;
×
UNCOV
519
                break;
×
520
            case RSA_SHA_256:
521
            default:
522
                break;
523
        }
524

525
        return algorithmId;
1✔
526
    }
527
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc