AlkantarClanX12
Your IP : 216.73.217.24
<?php
/**
* Connect service.
*
* @package PopupMaker
* @copyright Copyright (c) 2024, Code Atlantic LLC
*/
namespace PopupMaker\Services;
use PopupMaker\Base\Service;
use function PopupMaker\plugin;
defined( 'ABSPATH' ) || exit;
/**
* Connection management.
*
* NOTE: For wordpress.org admins: This is not called in the free, hosted version. This is only used if:
* - The user explicitly entered a license key.
* - This then opens a window to our site allowing the user to authorize one-time oauth-style connection & installation of pro.
*
* @package PopupMaker
*/
class Connect extends Service {
const API_URL = 'https://upgrade.wppopupmaker.com/';
const TOKEN_OPTION_NAME = 'popup_maker_connect_token';
const NONCE_OPTION_NAME = 'popup_maker_connect_nonce';
const ERROR_REFERRER = 1;
const ERROR_AUTHENTICATION = 2;
const ERROR_USER_AGENT = 3;
const ERROR_SIGNATURE = 4;
const ERROR_NONCE = 5;
const ERROR_WEBHOOK_ARGS = 6;
/**
* Check if debug mode is enabled.
*
* @return bool
*/
public function debug_mode_enabled() {
return defined( '\WP_DEBUG' ) && \WP_DEBUG;
}
/**
* Generate a new authorizatin token.
*
* @return string
*/
public function generate_token() {
$token = hash( 'sha512', (string) wp_rand() );
\set_site_transient( self::TOKEN_OPTION_NAME, $token, HOUR_IN_SECONDS );
return $token;
}
/**
* Get the current token.
*
* @return string|false
*/
public function get_access_token() {
return \get_site_transient( self::TOKEN_OPTION_NAME );
}
/**
* Get the current nonce.
*
* @param string $token Token.
*
* @return string|false
*/
public function get_nonce_name( $token ) {
return self::NONCE_OPTION_NAME . '_' . $token;
}
/**
* Log a message to the debug log if enabled.
*
* Here to prevent constant conditional checks for the debug mode.
*
* @param string $message Message.
* @param string $type Type.
*
* @return void
*/
public function debug_log( $message, $type = 'INFO' ) {
if ( $this->debug_mode_enabled() ) {
plugin( 'logging' )->log( "Plugin\Connect.$type: $message" );
}
}
/**
* Get header Authorization
*
* @return null|string
*/
public function get_request_authorization_header() {
$headers = null;
if ( isset( $_SERVER['Authorization'] ) ) {
$headers = trim( sanitize_text_field( wp_unslash( $_SERVER['Authorization'] ) ) );
} elseif ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) { // Nginx or fast CGI.
$headers = trim( sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) ) );
} elseif ( function_exists( 'apache_request_headers' ) ) {
$request_headers = apache_request_headers();
// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization).
$request_headers = array_combine( array_map( 'ucwords', array_keys( $request_headers ) ), array_values( $request_headers ) );
if ( isset( $request_headers['Authorization'] ) ) {
$headers = trim( $request_headers['Authorization'] );
}
}
// Fallback: Some servers strip Authorization headers, check for custom headers
if ( ! $headers ) {
$fallback_headers = [
'HTTP_X_AUTH_TOKEN' => 'X-Auth-Token',
'HTTP_X_AUTH_TOKEN_UC' => 'X-AUTH-TOKEN (uppercase)', // This won't match, but keeping for clarity
'HTTP_HTTP_X_AUTH_TOKEN' => 'HTTP_X_AUTH_TOKEN (server format)',
];
foreach ( $fallback_headers as $server_key => $header_name ) {
if ( isset( $_SERVER[ $server_key ] ) ) {
$token = sanitize_text_field( wp_unslash( $_SERVER[ $server_key ] ) );
$headers = 'Bearer ' . $token;
// Using fallback header.
break;
}
}
}
return $headers;
}
/**
* Get access token from header.
*
* @return string|null
*/
public function get_request_token() {
$headers = $this->get_request_authorization_header();
// HEADER: Get the access token from the header.
if ( ! empty( $headers ) ) {
if ( preg_match( '/Bearer\s(\S+)/', $headers, $matches ) ) {
return trim( $matches[1], ', ' );
}
}
return null;
}
/**
* Get nonce from header.
*
* @return string|null
*/
public function get_request_nonce() {
$headers = $this->get_request_authorization_header();
// HEADER: Get the nonce from the header.
if ( ! empty( $headers ) ) {
if ( preg_match( '/Nonce\s(\S+)/', $headers, $matches ) ) {
return trim( $matches[1], ', ' );
}
}
return null;
}
/**
* Get the webhook signature header.
*
* @return string
*/
private function get_request_signature() {
if ( isset( $_SERVER['HTTP_X_CONTENTCONTROL_SIGNATURE'] ) ) {
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_CONTENTCONTROL_SIGNATURE'] ) );
}
if ( isset( $_SERVER['HTTP_X_POPUPMAKER_SIGNATURE'] ) ) {
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_POPUPMAKER_SIGNATURE'] ) );
}
return '';
}
/**
* Get request data used to verify the webhook signature.
*
* @param \WP_REST_Request|null $request Optional REST request object.
* @return array<string,mixed>
*/
private function get_signature_request_data( $request = null ) {
if ( $request instanceof \WP_REST_Request ) {
$json_params = $request->get_json_params();
if ( is_array( $json_params ) && ! empty( $json_params ) ) {
return $json_params;
}
$body_params = $request->get_body_params();
if ( is_array( $body_params ) && ! empty( $body_params ) ) {
return $body_params;
}
}
$request_data = json_decode( file_get_contents( 'php://input' ), true );
if ( is_array( $request_data ) && ! empty( $request_data ) ) {
return $request_data;
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing
return ! empty( $_POST ) ? $_POST : [];
}
/**
* Throw a connection validation exception for REST API handlers.
*
* @param int $error_no Error number.
* @param string $message Error message.
* @return void
* @throws \Exception If validation fails.
*/
private function throw_connection_error( $error_no, $message ) {
throw new \Exception( esc_html( $message ), (int) $error_no );
}
/**
* Normalize the sending domain header into a host.
*
* @param string $referer Sending domain or URL.
* @return string
*/
private function normalize_sending_domain( $referer ) {
$referer_host = wp_parse_url( $referer, PHP_URL_HOST );
if ( ! $referer_host && preg_match( '/^[a-z0-9.-]+$/i', $referer ) ) {
$referer_host = $referer;
}
return is_string( $referer_host ) ? strtolower( $referer_host ) : '';
}
/**
* Get allowed sending hosts.
*
* @return string[]
*/
private function get_allowed_sending_hosts() {
$allowed_hosts = [
'wppopupmaker.com',
'upgrade.wppopupmaker.com',
];
/**
* Filter the allowed hosts for signed Popup Maker install webhooks.
*
* @param string[] $allowed_hosts Allowed host names.
*/
$allowed_hosts = apply_filters( 'popup_maker/connect/allowed_sending_hosts', $allowed_hosts );
return array_values( array_filter( array_map( 'strtolower', (array) $allowed_hosts ) ) );
}
/**
* Assert the user agent is a Popup Maker upgrader.
*
* @return void
* @throws \Exception If user agent validation fails.
*/
private function assert_user_agent() {
$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
if ( empty( $user_agent ) || ! preg_match( '/^PopupMakerUpgrader\/\d+\.\d+\.\d+$/', $user_agent ) ) {
$this->throw_connection_error( self::ERROR_USER_AGENT, 'User agent invalid: ' . $user_agent );
}
}
/**
* Assert the request has an allowed sending domain.
*
* @return void
* @throws \Exception If referrer validation fails.
*/
private function assert_referrer() {
$referer = isset( $_SERVER['HTTP_X_SENDING_DOMAIN'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_SENDING_DOMAIN'] ) ) : '';
if ( ! $referer ) {
$this->throw_connection_error( self::ERROR_REFERRER, 'Missing referrer' );
}
$referer_host = $this->normalize_sending_domain( $referer );
if ( ! $referer_host ) {
$this->throw_connection_error( self::ERROR_REFERRER, 'Invalid referrer' );
}
if ( ! in_array( $referer_host, $this->get_allowed_sending_hosts(), true ) ) {
$this->throw_connection_error( self::ERROR_REFERRER, 'Referrer doesn\'t match' );
}
}
/**
* Assert the bearer token matches the locally stored token.
*
* @return void
* @throws \Exception If authentication validation fails.
*/
private function assert_authentication() {
$token = $this->get_access_token();
$auth_token = $this->get_request_token();
if ( ! $token || ! $auth_token ) {
$this->throw_connection_error( self::ERROR_AUTHENTICATION, 'Missing authentication' );
}
if ( ! hash_equals( $token, $auth_token ) ) {
$this->throw_connection_error( self::ERROR_AUTHENTICATION, 'Invalid authentication' );
}
}
/**
* Assert the request signature matches the signed payload.
*
* @param \WP_REST_Request|null $request Optional REST request object.
* @return void
* @throws \Exception If signature validation fails.
*/
private function assert_signature( $request = null ) {
$signature = $this->get_request_signature();
if ( empty( $signature ) ) {
$this->throw_connection_error( self::ERROR_SIGNATURE, 'Missing signature' );
}
$request_data = $this->get_signature_request_data( $request );
$expected_signature = $this->generate_hash( $request_data, $this->get_access_token() );
if ( ! hash_equals( $expected_signature, $signature ) ) {
$this->throw_connection_error( self::ERROR_SIGNATURE, 'Invalid signature' );
}
}
/**
* Get the OAuth connect URL.
*
* @param string $license_key License key.
*
* @return array{url:string,back_url:string}
*/
public function get_connect_info( $license_key ) {
$token = $this->generate_token();
$nonce = wp_create_nonce( $this->get_nonce_name( $token ) );
$webhook = rest_url( 'popup-maker/v2/connect/install' );
$redirect = add_query_arg(
[
'post_type' => 'popup',
'page' => 'pum-settings',
// TODO This should be set to the Pro upgrade page?
'view' => 'upgrade',
'tab' => 'general',
],
admin_url( 'edit.php' )
);
$url = add_query_arg(
[
'key' => $license_key,
'token' => $token,
'nonce' => $nonce,
'webhook' => $webhook,
'version' => plugin( 'version' ),
'siteurl' => admin_url(),
'homeurl' => home_url(),
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
'redirect' => rawurlencode( base64_encode( $redirect ) ),
],
self::API_URL
);
$this->debug_log( 'Generated new connection.' );
$this->debug_log( 'Token: ' . $token );
$this->debug_log( 'Nonce: ' . $nonce );
$this->debug_log( 'Redirect URL: ' . $redirect );
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$this->debug_log( 'Base64 Redirect: ' . base64_encode( $redirect ) );
$this->debug_log( 'Final URL: ' . $url );
return [
'url' => $url,
'back_url' => add_query_arg(
[
'action' => 'popup_maker_connect',
'token' => $token,
],
$webhook
),
];
}
/**
* Kill the connection with no permission.
*
* @param int $error_no Error number.
* @param string|false $message Error message.
*
* @return void
*/
public function kill_connection( $error_no = self::ERROR_REFERRER, $message = false ) {
$this->debug_log( "Killing connection with error ($error_no) message: " . $message, 'ERROR' );
wp_die(
esc_html(
$this->debug_mode_enabled() && $message ?
$message :
__( 'Sorry, You Are Not Allowed to Access This Page.', 'popup-maker' )
),
esc_attr( (string) $error_no ),
[ 'response' => 403 ]
);
}
/**
* Verify the user agent.
*
* @return void
*/
public function verify_user_agent() {
try {
$this->assert_user_agent();
} catch ( \Exception $e ) {
$this->kill_connection( self::ERROR_USER_AGENT, $e->getMessage() );
}
}
/**
* Verify the referrer.
*
* @return void
*/
public function verify_referrer() {
try {
$this->assert_referrer();
} catch ( \Exception $e ) {
$this->kill_connection( self::ERROR_REFERRER, $e->getMessage() );
}
}
/**
* Verify the nonce.
*
* @deprecated 2.0.0 Don't use, it doesn't work as its a separate server making request.
*
* @return void
*/
public function verify_nonce() {
$token = $this->get_access_token();
$nonce = $this->get_request_nonce();
if ( ! $nonce ) {
$this->kill_connection( self::ERROR_NONCE, 'Missing nonce' );
}
if ( false === wp_verify_nonce( $nonce, $this->get_nonce_name( $token ) ) ) {
$this->debug_log( 'Nonce mismatch: ' . $nonce, 'DEBUG' );
$this->debug_log( 'Nonce Name: ' . $this->get_nonce_name( $token ) );
$this->kill_connection( self::ERROR_NONCE, 'Invalid nonce' );
}
}
/**
* Verify the authentication token.
*
* @return void
*/
public function verify_authentication() {
try {
$this->assert_authentication();
} catch ( \Exception $e ) {
$this->kill_connection( self::ERROR_AUTHENTICATION, $e->getMessage() );
}
}
/**
* Generate signature hash.
*
* This must match the hash generated on the server.
*
* @see \PopupMakerUpgrader\App::generate_hash()
*
* @param array<string,mixed>|string $data Data to hash.
* @param string $token Token to hash with.
* @return string
*/
public function generate_hash( $data, $token ) {
if ( ! is_string( $data ) ) {
// Convert boolean values to their string representation.
array_walk_recursive(
$data,
function ( &$value ) {
if ( is_bool( $value ) ) {
$value = $value ? '1' : '0';
}
}
);
// Sort the array before encoding it as JSON.
ksort( $data );
// Ignored due to wp_json_encode stripping slashes and breaking hashes.
// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
$data = json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
}
// Generate the hash binary.
$hash = hash_hmac( 'sha256', $data, $token, true );
// Generate HMAC-SHA256 signature.
// Encode the hash in base64 to make it URL safe.
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return base64_encode( $hash );
}
/**
* Verify the signature of the requester.
*
* @return void
*/
public function verify_signature() {
try {
$this->assert_signature();
} catch ( \Exception $e ) {
$this->kill_connection( self::ERROR_SIGNATURE, $e->getMessage() );
}
}
/**
* Validate the connection.
*
* @return void
*/
public function validate_connection() {
// Validate connection security layers.
$this->verify_user_agent();
$this->verify_referrer();
$this->verify_authentication();
$this->verify_signature();
}
/**
* Validate a REST webhook connection.
*
* @param \WP_REST_Request|null $request Optional REST request object.
* @return void
* @throws \Exception If validation fails.
*/
public function validate_rest_connection( $request = null ) {
$this->assert_user_agent();
$this->assert_referrer();
$this->assert_authentication();
$this->assert_signature( $request );
}
/**
* Verify the connection.
*
* @return void
*/
public function process_verify_connection() {
$this->validate_connection();
wp_send_json_success();
}
/**
* Get the webhook args.
*
* @return array{file:string,type:string,slug:string,force:boolean}
*/
public function get_webhook_args() {
// First try to get data from JSON body (modern REST API approach)
$json_data = json_decode( file_get_contents( 'php://input' ), true );
if ( is_array( $json_data ) && ! empty( $json_data ) ) {
$this->debug_log( 'Using JSON body data for webhook args', 'DEBUG' );
$this->debug_log( 'JSON data: ' . wp_json_encode( $json_data, JSON_PRETTY_PRINT ), 'DEBUG' );
$args = [
'file' => ! empty( $json_data['file'] ) ? esc_url_raw( $json_data['file'] ) : '',
'type' => ! empty( $json_data['type'] ) ? sanitize_text_field( $json_data['type'] ) : 'plugin',
'slug' => ! empty( $json_data['slug'] ) ? sanitize_text_field( $json_data['slug'] ) : '',
'force' => ! empty( $json_data['force'] ) ? (bool) $json_data['force'] : false,
];
} else {
// Fallback to $_REQUEST for backwards compatibility
$this->debug_log( 'Using $_REQUEST data for webhook args', 'DEBUG' );
$args = [
// phpcs:disable WordPress.Security.NonceVerification.Recommended
'file' => ! empty( $_REQUEST['file'] ) ? esc_url_raw( wp_unslash( $_REQUEST['file'] ) ) : '',
'type' => ! empty( $_REQUEST['type'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['type'] ) ) : 'plugin',
'slug' => ! empty( $_REQUEST['slug'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['slug'] ) ) : '',
'force' => ! empty( $_REQUEST['force'] ) ? (bool) $_REQUEST['force'] : false,
// phpcs:enable WordPress.Security.NonceVerification.Recommended
];
}
$this->debug_log( 'Parsed webhook args: ' . wp_json_encode( $args, JSON_PRETTY_PRINT ), 'DEBUG' );
$this->verify_webhook_args( $args );
return $args;
}
/**
* Verify and return webhook args.
*
* @param array{file:string,type:string,slug:string,force:bool} $args The webhook args.
*
* @return void
*/
public function verify_webhook_args( $args ) {
$file_url = ! empty( $args['file'] ) ? $args['file'] : false;
$file_type = ! empty( $args['type'] ) ? $args['type'] : false;
$file_slug = ! empty( $args['slug'] ) ? $args['slug'] : false;
$force = ! empty( $args['force'] ) ? (bool) $args['force'] : false;
if ( ! $file_url || ! $file_type || ! $file_slug ) {
$this->kill_connection( self::ERROR_WEBHOOK_ARGS, 'Missing webhook args' );
}
if ( ! in_array( $file_type, [ 'plugin', 'theme' ], true ) ) {
$this->kill_connection( self::ERROR_WEBHOOK_ARGS, 'Invalid webhook args' );
}
}
/**
* Listen for incoming secure webhooks from the API server.
*
* @return void
*/
public function process_webhook() {
// 1. Validate the connection is secure & from the API server.
$this->validate_connection();
$error = esc_html__( 'There was an error while installing an upgrade. Please download the plugin from wppopupmaker.com and install it manually.', 'popup-maker' );
// 2. Get the webhook data.
$args = $this->get_webhook_args();
// 3. Validate license key.
if ( ! plugin( 'license' )->is_license_active() ) {
$this->debug_log( 'License not active', 'DEBUG' );
wp_send_json_error( $error );
}
// Set the current screen to avoid undefined notices.
set_current_screen( 'settings_page_popup-maker-settings' );
// 4. Install the plugin.
switch ( $args['type'] ) {
case 'plugin':
$this->install_plugin( $args );
break;
}
// 5. Delete the token to prevent abuse. Doing so last means it should be possible to retry if something goes wrong.
if ( ! $this->debug_mode_enabled() ) {
$this->debug_log( 'Deleting token', 'DEBUG' );
\delete_site_transient( self::TOKEN_OPTION_NAME );
}
}
/**
* Install a plugin.
*
* @param array{file:string,type:string,slug:string,force:bool} $args The file args.
* @return void
*/
public function install_plugin( $args ) {
$this->debug_log( 'Installing plugin...', 'DEBUG' );
// If not forcing, and already active, return success.
if ( ! $args['force'] && is_plugin_active( "{$args['slug']}/{$args['slug']}.php" ) ) {
$this->debug_log( 'Plugin already installed & active.', 'DEBUG' );
wp_send_json_success( esc_html__( 'Plugin installed & activated.', 'popup-maker' ) );
}
// Get the upgrader.
$upgrader = $this->container->get( 'upgrader' );
// Install the plugin. (if installed already, this will replace it using upgrade).
$installed = $upgrader->install_plugin( $args['file'] );
if ( ! is_wp_error( $installed ) ) {
$this->debug_log( 'Plugin installed & activated successfully.', 'DEBUG' );
wp_send_json_success( esc_html__( 'Plugin installed & activated.', 'popup-maker' ) );
}
switch ( $installed->get_error_code() ) {
default:
$error = $installed->get_error_message();
}
if ( empty( $error ) ) {
$error = esc_html__( 'There was an error while installing an upgrade. Please download the plugin from wppopupmaker.com and install it manually.', 'popup-maker' );
}
$this->debug_log( 'Plugin install failed: ' . $error, 'DEBUG' );
wp_send_json_error( $installed->get_error_message() );
}
/**
* Verify webhook request for REST API.
*
* Validates webhook requests coming through REST endpoints.
* This method performs the same security checks as the AJAX webhook
* but is designed for REST API integration.
*
* @param \WP_REST_Request $request REST request object.
* @return array{valid:bool,error:string|null}
*/
public function verify_webhook_request( $request ) {
$this->debug_log( 'Verifying webhook request via REST API...', 'DEBUG' );
try {
// Validate the connection security.
$this->validate_rest_connection( $request );
// Validate license is active.
if ( ! plugin( 'license' )->is_license_active() ) {
$this->debug_log( 'License not active for webhook request', 'DEBUG' );
return [
'valid' => false,
'error' => 'License not active',
];
}
$this->debug_log( 'Webhook request verified successfully', 'DEBUG' );
return [
'valid' => true,
'error' => null,
];
} catch ( \Exception $e ) {
$this->debug_log( 'Webhook verification failed: ' . $e->getMessage(), 'ERROR' );
return [
'valid' => false,
'error' => $e->getMessage(),
];
}
}
/**
* Generate webhook URL for REST endpoints.
*
* Creates properly formatted webhook URLs for REST API endpoints
* with appropriate namespace and security parameters.
*
* @param string $endpoint The REST endpoint (e.g., 'connect/verify', 'upgrade/install').
* @param array $args Additional query arguments.
* @return string The webhook URL.
*/
public function generate_webhook_url( $endpoint, $args = [] ) {
// Generate security token.
$token = $this->generate_token();
$nonce = wp_create_nonce( $this->get_nonce_name( $token ) );
// Base REST URL with our namespace.
$base_url = rest_url( 'popup-maker/v2/' . ltrim( $endpoint, '/' ) );
// Add security parameters.
$default_args = [
'token' => $token,
'nonce' => $nonce,
];
$query_args = array_merge( $default_args, $args );
$webhook_url = add_query_arg( $query_args, $base_url );
$this->debug_log( 'Generated webhook URL: ' . $webhook_url );
$this->debug_log( 'Token: ' . $token );
$this->debug_log( 'Nonce: ' . $nonce );
return $webhook_url;
}
}
Home - Capital GMC Buick Regina
Skip to content
{{ $t(category) }}
Error
{{vehicle.modelData.year}} {{vehicle.modelData.make}} {{vehicle.modelData.model}}
Starting from {{vehicle.modelData.startingPrice | moneyFormat(lang)}}
Welcome to Capital GMC BUICK – REGINA
Thank you for choosing Capital GMC Buick | Regina, your premier certified Buick and GMC dealership proudly serving drivers in Regina and the surrounding communities. Whether you’re searching for a brand-new Buick or GMC vehicle or a meticulously inspected pre-owned model, we have a diverse selection to match your needs and lifestyle.
Beyond our impressive inventory, we offer a seamless and stress-free financing experience through our well-connected finance centre, where our team of experts is dedicated to securing the best loan or lease options for you, quickly, transparently, and hassle-free.
But our commitment to you doesn’t stop at the sale. Our state-of-the-art service centre is staffed with skilled Buick and GMC technicians who use the latest equipment and genuine OEM parts to keep your vehicle running at its best. From routine maintenance to complex repairs, we’ve got you covered.
Experience top-tier customer service, quality vehicles, and expert care, all in one place. Visit Capital GMC Buick | Regina today or call us at 306-205-8072 with any questions. We’re here to help!
Ask a Question
Capital GMC Buick – Regina
Contact Us
Have a question or need assistance? Fill out the form and we will reach out to you as soon as possible.
Notice: JavaScript is required for this content.
CLOSE
Schedule a Visit
Let us know when you are coming and how we can assist you. We can ensure someone will be on hand to help you out at the desired date and time.
Notice: JavaScript is required for this content.
CLOSE
Find a Career
Have a look at our list of available positions and apply online today to join our team!
×
Opportunities to Grow
The auto industry is constantly changing and we want to continue to grow. We offer growth, leadership & mentorship programs to allow our staff to grow with us.
×
Competitive Salary
We have a significant earning potential with incentive-based pay in most roles. We also offer an employee referral bonus with paid bonuses.
×
Health & Dental
We offer a comprehensive benefits package including extended health, dental, and vision care. We also include paramedical, life insurance, paid sick leave, short & long-term disability coverages.
×
Vacation
We value our employees and want everyone to take their vacation time. We offer a minimum of 2 weeks vacation each year.
×
Training & Development
We have many opportunities for paid education and training in-house as well as training from the Manufacturer.
×
$10,000 Cash Giveaway – Terms & Conditions
All October long, stop by Capital GMC Buick Cadillac, to enter for your chance to win $10,000 cash. No purchase is required, but entries must be made in-store.
The contest is open to residents of Saskatchewan who are 18+. Dealership employees and their households are not eligible. Entries will be accepted from October 1 to October 31, 2025. A random draw will take place on November 1, 2025.
The prize is one $10,000 award, paid by cheque, and must be accepted as awarded. Winner will be contacted by phone or email and must respond within 7 days or another entry may be drawn. Odds of winning depend on the number of entries received.By entering, you agree that Capital Automotive Group may use your name and photo for winner announcements. The contest is governed by the laws of Saskatchewan.
Notice: JavaScript is required for this content.
CLOSE