public String getMqttOverWebsocketsUriString()

in src/main/java/com/awslabs/aws/iot/websockets/BasicMqttOverWebsocketsProvider.java [125:265]


    public String getMqttOverWebsocketsUriString(Optional<MqttOverWebsocketsUriConfig> optionalMqttOverWebsocketsUriConfig) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
        Optional<String> optionalScopeDownJson = Optional.empty();

        if (optionalMqttOverWebsocketsUriConfig.isPresent()) {
            // We have a websockets config, have they've specified both the policy JSON and the policy object?
            MqttOverWebsocketsUriConfig mqttOverWebsocketsUriConfig = optionalMqttOverWebsocketsUriConfig.get();
            if (mqttOverWebsocketsUriConfig.optionalScopeDownPolicy().isPresent() &&
                    mqttOverWebsocketsUriConfig.optionalScopeDownPolicyJson().isPresent()) {
                throw new RuntimeException("Scope down policy object and scope down policy JSON can not be used simultaneously. Use either a scope down policy object or scope down policy JSON but not both.");
            }

            if (mqttOverWebsocketsUriConfig.optionalScopeDownPolicy().isPresent()) {
                optionalScopeDownJson = mqttOverWebsocketsUriConfig.optionalScopeDownPolicy().map(ScopeDownPolicy::toString);
            } else if (mqttOverWebsocketsUriConfig.optionalScopeDownPolicyJson().isPresent()) {
                optionalScopeDownJson = mqttOverWebsocketsUriConfig.optionalScopeDownPolicyJson();
            }
        }

        long time = System.currentTimeMillis();
        DateTime dateTime = new DateTime(time);
        String dateStamp = getDateStamp(dateTime);
        String amzdate = getAmzDate(dateTime);
        String service = "iotdata";
        Optional<Region> optionalRegion = optionalMqttOverWebsocketsUriConfig.flatMap(MqttOverWebsocketsUriConfig::optionalRegion);
        Optional<AwsCredentialsProviderChain> optionalAwsCredentialsProviderChain = optionalMqttOverWebsocketsUriConfig.flatMap(MqttOverWebsocketsUriConfig::optionalAwsCredentialsProviderChain);
        Region region = optionalRegion.orElseGet(this::getDefaultRegionString);
        String regionString = region.toString();
        String clientEndpoint = optionalMqttOverWebsocketsUriConfig
                .flatMap(MqttOverWebsocketsUriConfig::optionalEndpointAddress)
                .flatMap(EndpointAddress::getEndpointAddress)
                .orElseGet(() -> getEndpointAddressForRegion(optionalRegion));

        AwsCredentials awsCredentials;
        String awsAccessKeyId;
        String awsSecretAccessKey;
        Optional<String> optionalSessionToken = Optional.empty();

        Optional<String> optionalRoleToAssume = optionalMqttOverWebsocketsUriConfig
                .flatMap(MqttOverWebsocketsUriConfig::optionalRoleToAssume)
                .flatMap(RoleToAssume::getRoleToAssume);

        StsClient stsClient = getStsClient.apply(optionalAwsCredentialsProviderChain, optionalRegion);

        if (!optionalRoleToAssume.isPresent()) {
            if (optionalScopeDownJson.isPresent()) {
                // There is a scope down policy, get a federation token with it

                // Trim the UUID down to a size that STS will accept
                String name = UUID.randomUUID().toString().substring(0, 31);

                GetFederationTokenRequest getFederationTokenRequest = GetFederationTokenRequest.builder()
                        .name(name)
                        .policy(optionalScopeDownJson.get())
                        .build();

                GetFederationTokenResponse getFederationTokenResponse = stsClient.getFederationToken(getFederationTokenRequest);

                Credentials credentials = getFederationTokenResponse.credentials();

                awsAccessKeyId = credentials.accessKeyId();
                awsSecretAccessKey = credentials.secretAccessKey();
                optionalSessionToken = Optional.of(credentials.sessionToken());
            } else {
                // No scope down policy, just use the user's existing permissions
                awsCredentials = credentialsProvider.resolveCredentials();
                awsAccessKeyId = awsCredentials.accessKeyId();
                awsSecretAccessKey = awsCredentials.secretAccessKey();

                if (awsCredentials instanceof AwsSessionCredentials) {
                    optionalSessionToken = Optional.of(((AwsSessionCredentials) awsCredentials).sessionToken());
                }
            }
        } else {
            // Assume a new role
            String roleArn = optionalRoleToAssume.get();

            if (!roleArn.startsWith(ARN_AWS_IAM)) {
                // The role coming from the environment will be the full ARN, if this is just the role name add the proper prefix
                String accountId = getAccountId.apply(stsClient);

                roleArn = ARN_AWS_IAM + accountId + ":role/" + roleArn;
            }

            log.debug("Attempting to assume role: " + roleArn);

            AssumeRoleRequest.Builder assumeRoleRequestBuilder = AssumeRoleRequest.builder()
                    .roleArn(roleArn)
                    .roleSessionName("aws-iot-core-websockets-java");

            // Add the scope down policy if there is one
            optionalScopeDownJson.ifPresent(assumeRoleRequestBuilder::policy);

            AssumeRoleRequest assumeRoleRequest = assumeRoleRequestBuilder.build();
            AssumeRoleResponse assumeRoleResult = stsClient.assumeRole(assumeRoleRequest);

            Credentials credentials = assumeRoleResult.credentials();
            awsSecretAccessKey = credentials.secretAccessKey();
            awsAccessKeyId = credentials.accessKeyId();
            optionalSessionToken = Optional.of(credentials.sessionToken());
        }

        String algorithm = "AWS4-HMAC-SHA256";
        String method = "GET";
        String canonicalUri = "/mqtt";

        String credentialScope = dateStamp + "/" + regionString + "/" + service + "/" + "aws4_request";

        Tuple2<String, String> xAmzAlgorithm = Tuple.of("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
        Tuple2<String, String> xAmzCredential = Tuple.of("X-Amz-Credential", URLEncoder.encode(awsAccessKeyId + "/" + credentialScope, StandardCharsets.UTF_8));
        Tuple2<String, String> xAmzDate = Tuple.of("X-Amz-Date", amzdate);
        Tuple2<String, String> xAmzSignedHeaders = Tuple.of(X_AMZ_SIGNED_HEADERS, "host");

        String canonicalQueryString = String.join("&",
                xAmzAlgorithm.apply(this::tupleToParameter),
                xAmzCredential.apply(this::tupleToParameter),
                xAmzDate.apply(this::tupleToParameter),
                xAmzSignedHeaders.apply(this::tupleToParameter));

        String canonicalHeaders = "host:" + clientEndpoint + ":443\n";
        String payloadHash = sha256("");
        String canonicalRequest = method + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\nhost\n" + payloadHash;

        String stringToSign = algorithm + "\n" + amzdate + "\n" + credentialScope + "\n" + sha256(canonicalRequest);
        byte[] signingKey = getSignatureKey(awsSecretAccessKey, dateStamp, regionString, service);
        String signature = sign(signingKey, stringToSign);

        Tuple2<String, String> xAmzSignature = Tuple.of("X-Amz-Signature", signature);

        canonicalQueryString = String.join("&", canonicalQueryString,
                xAmzSignature.apply(this::tupleToParameter));

        if (optionalSessionToken.isPresent()) {
            Tuple2<String, String> xAmzSecurityToken = Tuple.of("X-Amz-Security-Token", URLEncoder.encode(optionalSessionToken.get(), StandardCharsets.UTF_8));

            canonicalQueryString = String.join("&",
                    canonicalQueryString,
                    xAmzSecurityToken.apply(this::tupleToParameter));
        }

        return "wss://" + clientEndpoint + canonicalUri + "?" + canonicalQueryString;
    }