diff --git a/v1/Application.cfc b/v1/Application.cfc index 2edd739..1ad2cfe 100644 --- a/v1/Application.cfc +++ b/v1/Application.cfc @@ -51,7 +51,7 @@ //variables.framework.docs={}; variables.framework.docs.APIName="Deck API"; - variables.framework.docs.APIVersion="0.062"; + variables.framework.docs.APIVersion="0.064"; variables.framework.globalHeaders = structNew(); variables.framework.globalHeaders["Access-Control-Expose-Headers"] = "Location"; diff --git a/v1/lib/TokenGenerator.cfc b/v1/lib/TokenGenerator.cfc new file mode 100644 index 0000000..d67bfb9 --- /dev/null +++ b/v1/lib/TokenGenerator.cfc @@ -0,0 +1,150 @@ +component /*https://www.bennadel.com/blog/2976-trying-to-generate-cryptographically-strong-random-tokens-in-coldfusion.htm*/ + output = false + hint = "I generate random tokens using Java's SecureRandom class." + { + + /** + * I initialize the token generator. + * + * @output false + */ + public any function init() { + + // I am the generator implementation used to generate the random token data. + // -- + // NOTE: The SHA1PRNG is not the default in all implementations of the JVM. As + // such, we're defining the algorithm explicitly to keep this code consistent. + generator = createObject( "java", "java.security.SecureRandom" ) + .getInstance( + javaCast( "string", "SHA1PRNG" ), + javaCast( "string", "SUN" ) + ) + ; + + // Now that we've initialized the random generator, we have to generate a random + // byte of data. This will ensure that the random generator is self-seeded using + // a shared seed generator. + // -- + // CAUTION: Since the underlying seed generator reads from sources of entropy, + // this may hang until enough entropy has been collected. This is another good + // reason to do it during initialization time rather than at first-use time. + generator.nextBytes( charsetDecode( " ", "utf-8" ) ); + + // I hold the future date at which time the generator should be reseeded in order + // to keep it unpredictable. + // -- + // NOTE: This doesn't affect the randomness of the values. But, the thinking + // is that the longer the generator is producing values using the same seed, + // the more likely an attacker is to be able to determine the original seed by + // passively observing generated values. + reseedAt = getNextReseedAt(); + + return( this ); + + } + + + // --- + // PUBLIC METHODS. + // --- + + + /** + * I generate "cryptographically strong" random token strings that are based on the + * given number of random bytes. The random bytes are subsequently encoded using a + * base64url character-set so that they are URL-safe and can be used in a variety of + * contexts. And, since base65url is a case-sensitive schema, the tokens will + * naturally be case-sensitive. + * + * @byteCount I am the number of random bytes used to generate the token. + * @output false + */ + public string function nextToken( numeric byteCount = 32 ) { + + // Check to see if the generator needs to be reseeded (using a double-check + // locking approach to reduce the bottleneck). + if ( now() >= reseedAt ) { + + // Synchronize the reseeding. + // -- + // NOTE: From what I have read, I DON'T BELIEVE that .generateSeed() will + // ever hang with the SHA1PRNG algorithm. However, it is unclear to me. As + // such, I'm using [throwOnTimeout = false] so that parallel threads won't + // error if the lock cannot be obtained in a timely manner and will just + // fall through to using the generator with the pre-seeding state. + lock + name = "TokenGenerator.reseedCheck" + type = "exclusive" + timeout = 1 + throwOnTimeout = false + { + + // Perform double-check - generator may have already been reseeded by + // a parallel request. + if ( now() >= reseedAt ) { + + reseedAt = getNextReseedAt(); + + // NOTE: Once the generator was seeded internally, this re-seeding + // will only ever "add to" the existing seed. As such, this is still + // building on top of the original randomness and calling this, on + // interval, this will never reduce randomness. + generator.setSeed( generator.generateSeed( javaCast( "int", 32 ) ) ); + + } + + } // END: Lock. + + } + + // Create the byte buffer into which the random bytes will be written. Since + // there's no "correct" way to generate a byte array in ColdFusion, we can + // generate a string of the desired length and then decode it into bytes. + var byteBuffer = charsetDecode( repeatString( " ", byteCount ), "utf-8" ); + + generator.nextBytes( byteBuffer ); + + return( encodeBytes( byteBuffer ) ); + + } + + + // --- + // PRIVATE METHODS. + // --- + + + /** + * I encode the given byte array using the base64url character-set. + * + * @bytes I am the byte array being encoded. + * @output false + */ + private string function encodeBytes( required binary bytes ) { + + var token = binaryEncode( bytes, "base64" ); + + // Replace the characters that are not allowed in the base64url format. The + // characters [+, /, =] are removed for URL-based base64 values because they + // have significant meaning in the context of URL paths and query-strings. + token = replace( token, "+", "-", "all" ); + token = replace( token, "/", "_", "all" ); + token = replace( token, "=", "", "all" ); + + return( token ); + + } + + + /** + * I calculate the next date of reseeding. + * + * @output false + */ + private date function getNextReseedAt() { + + return( dateAdd( "h", 1, now() ) ); + + } + +} \ No newline at end of file diff --git a/v1/resources/instance_ls.cfc b/v1/resources/instance_ls.cfc index e29888e..e19992b 100644 --- a/v1/resources/instance_ls.cfc +++ b/v1/resources/instance_ls.cfc @@ -79,6 +79,7 @@ + select count(*) as cnt from ( @@ -111,7 +112,7 @@ (select to_char(io.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as operation_dt_start (select to_char(io.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as operation_dt_finish (select round(extract('epoch' from coalesce(io.dt_finish,CURRENT_TIMESTAMP)- io.dt_start),1) from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as duration - (select case when io.is_successful then extract('epoch' from CURRENT_TIMESTAMP - coalesce(io.dt_finish,CURRENT_TIMESTAMP)) else 0 end from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as uptime + (select case when io.operation IN ('delete', 'suspend') AND io.is_successful then 0 /*null*/ when io.is_successful then extract('epoch' from CURRENT_TIMESTAMP - coalesce(io.dt_finish,CURRENT_TIMESTAMP)) else 0 end from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as uptime/* *** это вообще неправильно, некоторые операции заканчиваются состоянием running, а некоторые нет, переделать */ (select to_char(io.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_operation io where io.instance_uid=e.instance_uid AND io.is_successful AND io.operation='create' order by dt_submit desc limit 1) as dt_deploy (select instance_data::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_data (select st.instance_state_uid::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_state_uid @@ -188,7 +189,7 @@ (select to_char(io.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as operation_dt_start (select to_char(io.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as operation_dt_finish (select round(extract('epoch' from coalesce(io.dt_finish,CURRENT_TIMESTAMP)- io.dt_start),1) from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as duration - (select case when io.is_successful then extract('epoch' from CURRENT_TIMESTAMP - coalesce(io.dt_finish,CURRENT_TIMESTAMP)) else 0 end from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as uptime + (select case when io.operation IN ('delete', 'suspend') AND io.is_successful then 0 /*null*/ when io.is_successful then extract('epoch' from CURRENT_TIMESTAMP - coalesce(io.dt_finish,CURRENT_TIMESTAMP)) else 0 end from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as uptime/* *** это вообще неправильно, некоторые операции заканчиваются состоянием running, а некоторые нет, переделать */ (select to_char(io.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_operation io where io.instance_uid=e.instance_uid AND io.is_successful AND io.operation='create' order by dt_submit desc limit 1) as dt_deploy (select instance_data::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_data diff --git a/v1/resources/instance_operation_default.cfc b/v1/resources/instance_operation_default.cfc index 368a720..57f47cf 100644 --- a/v1/resources/instance_operation_default.cfc +++ b/v1/resources/instance_operation_default.cfc @@ -91,6 +91,7 @@ + @@ -100,6 +101,12 @@ + + + + +