064 instance_ls uptime for deleted, suspended

This commit is contained in:
msyu
2025-03-10 06:05:55 +03:00
parent 66f56c74f7
commit 70331fc8e4
4 changed files with 161 additions and 3 deletions
+1 -1
View File
@@ -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";
+150
View File
@@ -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() ) );
}
}
+3 -2
View File
@@ -79,6 +79,7 @@
<!--- Такая бодяга. Кастомные сериализаторы не умеют вложенных структур (поразительно, или я не нашел как). Стандартный сериализатор не умеет выводить дату в ISO 8601. Приходится колхозить и форматировать на стороне БД (насилу подобрал формат). Также на стороне БД приходится конвертировать GUID, потому что драйвер pg jdbc его представляет как структуру из 2 чисел.
И то сказать, jsonb как-то странно сериализуется, в 3 поля, вместо одного Value
--->
<!--- ********* Внимание, ниже дублирование этого селекта! --->
<cfquery name="local.qCnt" result="local.result">
select count(*) as cnt
from (
@@ -111,7 +112,7 @@
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>/* *** это вообще неправильно, некоторые операции заканчиваются состоянием running, а некоторые нет, переделать */
<m:field>(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</m:field>
<m:field formatter=#function(x){return (deserializeJson(x))}#>(select instance_data::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_data</m:field><!--- *** операция выглядит очень накладно, но можно это поле в дельнейшем не выводить --->
<m:field>(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</m:field>
@@ -188,7 +189,7 @@
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>/* *** это вообще неправильно, некоторые операции заканчиваются состоянием running, а некоторые нет, переделать */
<m:field>(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</m:field>
<!--- *** не то же самое, что в DETAIL view! --->
<m:field formatter=#function(x){return (deserializeJson(x))}#>(select instance_data::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_data</m:field><!--- *** операция выглядит очень накладно, но можно это поле в дельнейшем не выводить --->
@@ -91,6 +91,7 @@
</cffunction>
<!--- *** выглядит ужасно, но красивый вариант не придумывается ряд месяцев --->
<!--- *** Внимание! нужно учитывать скоуп уникальности --->
<cffunction name="computeDefault">
<cfargument name="defaultCompute"/>
<cfargument name="original"/>
@@ -100,6 +101,12 @@
<cfswitch expression=#arguments.defaultCompute#>
<cfcase value="display_name">
<cfreturn CreateObject("component", "instance_ls").generateDefaultName(arguments.service_id,arguments.usr_id)/>
</cfcase>
<cfcase value="append_pseudorandom">
<!--- <cfset var generator=createObject('component', 'lib.TokenGenerator')/>
<cfset generator.init()/> --->
<cfset var generator = new lib.TokenGenerator()/>
<cfreturn "#arguments.original##lcase(generator.nextToken(6))#"/> <!--- почему-то длина отличается, чтобы получить 8, пишу 6 --->
</cfcase>
<cfdefaultcase>
<cfreturn #arguments.original#/>