$state, 'verificador' => $verificador, 'intencion' => $intencion, ), 10 * MINUTE_IN_SECONDS ); $parametros = array( 'cliente' => 'wordpress', 'sitio_url' => home_url(), 'nombre_sitio' => wp_strip_all_tags( get_bloginfo( 'name' ) ), 'redirect_uri' => self::url_de_regreso(), 'state' => $state, 'code_challenge' => $desafio, 'code_challenge_method' => 'S256', 'intencion' => $intencion, ); if ( 'produccion' === $intencion ) { $parametros['conexion_id'] = $estado['conexion_id']; } return add_query_arg( rawurlencode_deep( $parametros ), self::url_app( '/conectar/sitio' ) ); } /** * Procesa el regreso desde ValidaFirma (`validafirma_codigo` o `validafirma_error` y `state`). * El `state` hace de comprobación contra falsificación de petición: viene de un redirect externo y no admite nonce. * * @return string|null Código de aviso para mostrar, o null si no hay regreso que procesar. */ public static function procesar_regreso() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- regreso externo; la autenticidad la da `state`. if ( ! isset( $_GET['state'] ) || ( ! isset( $_GET['validafirma_codigo'] ) && ! isset( $_GET['validafirma_error'] ) ) ) { return null; } $state = sanitize_text_field( wp_unslash( $_GET['state'] ) ); $codigo = isset( $_GET['validafirma_codigo'] ) ? sanitize_text_field( wp_unslash( $_GET['validafirma_codigo'] ) ) : ''; $error = isset( $_GET['validafirma_error'] ) ? sanitize_key( wp_unslash( $_GET['validafirma_error'] ) ) : ''; // phpcs:enable if ( ! current_user_can( 'manage_options' ) ) { return 'sin_permiso'; } $intento = get_transient( self::clave_intento() ); delete_transient( self::clave_intento() ); if ( ! is_array( $intento ) || ! hash_equals( (string) $intento['state'], $state ) ) { return 'intento_vencido'; } if ( '' !== $error || '' === $codigo ) { return 'cancelado'; } $estado = self::estado(); if ( 'produccion' === $intento['intencion'] ) { if ( ! $estado ) { return 'error_conexion'; } $respuesta = Validafirma_Cliente_API::instancia()->canjear( $codigo, $intento['verificador'], self::url_de_regreso(), self::llave( 'prueba' ) ); } else { $respuesta = Validafirma_Cliente_API::instancia()->canjear( $codigo, $intento['verificador'], self::url_de_regreso() ); } if ( is_wp_error( $respuesta ) ) { $datos = Validafirma_Cliente_API::datos_de( $respuesta ); if ( 'TOPE_DE_LLAVES' === $datos['codigo'] ) { return 'tope_de_llaves'; } return 'error_conexion'; } $cuerpo = is_array( $respuesta['cuerpo'] ) ? $respuesta['cuerpo'] : array(); if ( empty( $cuerpo['llave'] ) || empty( $cuerpo['conexion_id'] ) ) { return 'error_conexion'; } if ( 'produccion' === $intento['intencion'] ) { $guardado = Validafirma_Secretos::poner( array( 'llave_produccion' => (string) $cuerpo['llave'], 'secreto_produccion' => isset( $cuerpo['secreto_webhooks'] ) ? (string) $cuerpo['secreto_webhooks'] : '', ) ); if ( ! $guardado ) { return 'error_conexion'; } $estado['modo'] = 'produccion'; $estado['produccion'] = true; update_option( self::OPCION, $estado, false ); return 'produccion'; } $guardado = Validafirma_Secretos::guardar( array( 'llave_prueba' => (string) $cuerpo['llave'], 'secreto_prueba' => isset( $cuerpo['secreto_webhooks'] ) ? (string) $cuerpo['secreto_webhooks'] : '', ) ); if ( ! $guardado ) { return 'error_conexion'; } $cuenta = isset( $cuerpo['cuenta'] ) && is_array( $cuerpo['cuenta'] ) ? $cuerpo['cuenta'] : array(); update_option( self::OPCION, array( 'conexion_id' => sanitize_text_field( (string) $cuerpo['conexion_id'] ), 'cuenta' => array( 'id' => isset( $cuenta['id'] ) ? sanitize_text_field( (string) $cuenta['id'] ) : '', 'nombre' => isset( $cuenta['nombre'] ) ? sanitize_text_field( (string) $cuenta['nombre'] ) : '', 'modalidad' => isset( $cuenta['modalidad'] ) && 'corporativa' === $cuenta['modalidad'] ? 'corporativa' : 'personal', ), 'sitio_url' => isset( $cuerpo['sitio_url'] ) ? esc_url_raw( (string) $cuerpo['sitio_url'] ) : home_url(), 'modo' => 'prueba', 'produccion' => false, 'conectada_en' => gmdate( 'c' ), 'estado' => 'conectada', ), false ); return 'conectado'; } /** * Cambia el modo del sitio sin ir a ValidaFirma («Volver a prueba» o «Volver a producción»). * * @param string $modo prueba | produccion. * @return bool */ public static function cambiar_modo( $modo ) { $estado = self::estado(); if ( ! $estado || ! in_array( $modo, array( 'prueba', 'produccion' ), true ) ) { return false; } if ( 'produccion' === $modo && ! self::tiene_produccion() ) { return false; } $estado['modo'] = $modo; return update_option( self::OPCION, $estado, false ) || get_option( self::OPCION ) === $estado; } /** * Marca la llave como revocada: el panel pide volver a conectar. * * @return void */ public static function marcar_llave_revocada() { $estado = self::estado(); if ( $estado && 'llave_revocada' !== $estado['estado'] ) { $estado['estado'] = 'llave_revocada'; update_option( self::OPCION, $estado, false ); } } /** * Desconecta: revoca las llaves en ValidaFirma y borra llaves y conexión del sitio. * Conserva la lista de envíos y los PDF (RN-20). * * @return true|WP_Error */ public static function desconectar() { $estado = self::estado(); if ( ! $estado ) { return true; } $modo = self::modo(); $respuesta = Validafirma_Cliente_API::instancia()->desconectar( $modo ); if ( is_wp_error( $respuesta ) ) { $datos = Validafirma_Cliente_API::datos_de( $respuesta ); // Si la llave ya no existe en ValidaFirma, la desconexión local es lo que falta. if ( 401 !== $datos['status'] ) { return $respuesta; } } Validafirma_Secretos::borrar(); delete_option( self::OPCION ); delete_transient( self::clave_intento() ); return true; } /** * Refresca los secretos de avisos (cron diario y ante un aviso que no verifica). * Si el secreto cambió, el anterior queda en gracia. * * @return void */ public static function refrescar_secretos() { $estado = self::estado(); if ( ! $estado || 'conectada' !== $estado['estado'] ) { return; } $modalidad = ''; foreach ( array( 'prueba', 'produccion' ) as $modo ) { if ( '' === self::llave( $modo ) ) { continue; } $respuesta = Validafirma_Cliente_API::instancia()->conexion_actual( $modo ); if ( is_wp_error( $respuesta ) || ! is_array( $respuesta['cuerpo'] ) ) { continue; } $cuerpo = $respuesta['cuerpo']; if ( empty( $cuerpo['secreto_webhooks'] ) ) { continue; } $actual = Validafirma_Secretos::obtener( 'secreto_' . $modo ); $nuevo = (string) $cuerpo['secreto_webhooks']; $cambio = array(); if ( $actual !== $nuevo ) { $cambio[ 'secreto_' . $modo . '_anterior' ] = $actual; $cambio[ 'secreto_' . $modo ] = $nuevo; } if ( ! empty( $cuerpo['secreto_webhooks_anterior'] ) ) { $cambio[ 'secreto_' . $modo . '_anterior' ] = (string) $cuerpo['secreto_webhooks_anterior']; } if ( $cambio ) { Validafirma_Secretos::poner( $cambio ); } if ( isset( $cuerpo['cuenta']['modalidad'] ) ) { $modalidad = 'corporativa' === $cuerpo['cuenta']['modalidad'] ? 'corporativa' : 'personal'; } } $vigente = self::estado(); if ( $modalidad && $vigente && $vigente['cuenta']['modalidad'] !== $modalidad ) { $vigente['cuenta']['modalidad'] = $modalidad; update_option( self::OPCION, $vigente, false ); } } }