diff --git a/RMQClient.xcodeproj/project.pbxproj b/RMQClient.xcodeproj/project.pbxproj index 4bf462e6..342cb3d8 100644 --- a/RMQClient.xcodeproj/project.pbxproj +++ b/RMQClient.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1029A0E22087A97E00C72924 /* ConnectionDeadlockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1029A0E12087A97E00C72924 /* ConnectionDeadlockTests.swift */; }; 4DF6E46D27F6E98100C43208 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEE7FE911C3BCA6000DF8C4F /* RMQClient.framework */; }; + 5CE0C5292B873842000087B7 /* ConnectionUpdateSecretTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE0C5282B873842000087B7 /* ConnectionUpdateSecretTest.swift */; }; 70311B0B21ED538600AE1804 /* RMQConnectionDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = 70311B0A21ED538600AE1804 /* RMQConnectionDefaults.h */; }; 70338A2421FBAA7C00C9069D /* TLSConnectionIntegrationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70338A2321FBAA7C00C9069D /* TLSConnectionIntegrationTest.swift */; }; 705359A921D174A400CF6456 /* TestCertificates in Resources */ = {isa = PBXBuildFile; fileRef = 705359A821D174A400CF6456 /* TestCertificates */; }; @@ -238,6 +239,7 @@ /* Begin PBXFileReference section */ 1029A0E12087A97E00C72924 /* ConnectionDeadlockTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionDeadlockTests.swift; sourceTree = ""; }; 4DF6E43627F6E90700C43208 /* MemoryTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MemoryTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 5CE0C5282B873842000087B7 /* ConnectionUpdateSecretTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionUpdateSecretTest.swift; sourceTree = ""; }; 70311B0A21ED538600AE1804 /* RMQConnectionDefaults.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RMQConnectionDefaults.h; sourceTree = ""; }; 70338A2321FBAA7C00C9069D /* TLSConnectionIntegrationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TLSConnectionIntegrationTest.swift; sourceTree = ""; }; 705359A821D174A400CF6456 /* TestCertificates */ = {isa = PBXFileReference; lastKnownFileType = folder; path = TestCertificates; sourceTree = SOURCE_ROOT; }; @@ -491,6 +493,7 @@ AE26FA2F1C970E9A00CDBBAA /* Channels */ = { isa = PBXGroup; children = ( + 5CE0C5282B873842000087B7 /* ConnectionUpdateSecretTest.swift */, AE26FA2D1C970DF100CDBBAA /* RMQChannelContract.swift */, AE8279841C8D86110013ADD1 /* ChannelAllocationTest.swift */, AE8AD2681CC545ED00229444 /* RMQFramesetValidatorTest.swift */, @@ -1292,6 +1295,7 @@ AE9DB00D1D12AA120005F44B /* RMQTransactionalConfirmationsTest.swift in Sources */, AE11F1B21D155C8A007AD330 /* RMQMessageTest.swift in Sources */, AE9DB0091D11FDED0005F44B /* ConfirmationsSpy.swift in Sources */, + 5CE0C5292B873842000087B7 /* ConnectionUpdateSecretTest.swift in Sources */, AEA8A7621CC97C7900371E38 /* RMQGCDSerialQueueTest.swift in Sources */, AE59345B1C8735FA00560A73 /* ChannelSpy.swift in Sources */, AE8AD25C1CC4E67F00229444 /* RMQUnallocatedChannelTest.swift in Sources */, diff --git a/RMQClient/RMQConnection.h b/RMQClient/RMQConnection.h index 3ce81f5e..799f4b93 100644 --- a/RMQClient/RMQConnection.h +++ b/RMQClient/RMQConnection.h @@ -242,4 +242,14 @@ */ - (nonnull id)createChannel; +/*! + * @brief Update secret. + * This method updates the secret used to authenticate this connection. + * It is used when secrets have an expiration date and need to be renewed, like OAuth 2 tokens. + * @param secret The new secret. + * @param reason The reason for the secret update. + */ +- (void)updateSecret:(nonnull NSString *)secret + reason:(nonnull NSString *)reason; + @end diff --git a/RMQClient/RMQConnection.m b/RMQClient/RMQConnection.m index 1700a9ba..59b49c7b 100644 --- a/RMQClient/RMQConnection.m +++ b/RMQClient/RMQConnection.m @@ -608,6 +608,14 @@ - (void)start { return ch; } +- (void)updateSecret:(NSString *)secret + reason:(NSString *)reason +{ + [self.commandQueue enqueue:^{ + [self sendFrameset:[[RMQFrameset alloc] initWithChannelNumber:@0 method:[self methodForUpdateSecret:secret reason:reason]]]; + }]; +} + - (BOOL)hasCompletedHandshake { return self.handshakeComplete; } @@ -746,6 +754,14 @@ - (void)closeAllUserChannels { } } +- (RMQConnectionUpdateSecret *)methodForUpdateSecret:(NSString *)secret + reason:(NSString *)reason { + RMQLongstr *secretLongstr = [[RMQLongstr alloc] init:secret]; + RMQShortstr *reasonShortstr = [[RMQShortstr alloc] init:reason]; + return [[RMQConnectionUpdateSecret alloc] initWithSecret:secretLongstr + reason:reasonShortstr]; +} + - (RMQConnectionClose *)amqClose { return [[RMQConnectionClose alloc] initWithReplyCode:[[RMQShort alloc] init:200] replyText:[[RMQShortstr alloc] init:@"Goodbye"] diff --git a/RMQClient/RMQMethodMap.m b/RMQClient/RMQMethodMap.m index 3ffbdd77..f80308d2 100644 --- a/RMQClient/RMQMethodMap.m +++ b/RMQClient/RMQMethodMap.m @@ -18,6 +18,8 @@ + (NSDictionary *)methodMap { @[@(10), @(51)] : [RMQConnectionCloseOk class], @[@(10), @(60)] : [RMQConnectionBlocked class], @[@(10), @(61)] : [RMQConnectionUnblocked class], + @[@(10), @(70)] : [RMQConnectionUpdateSecret class], + @[@(10), @(71)] : [RMQConnectionUpdateSecretOk class], @[@(20), @(10)] : [RMQChannelOpen class], @[@(20), @(11)] : [RMQChannelOpenOk class], @[@(20), @(20)] : [RMQChannelFlow class], diff --git a/RMQClient/RMQMethods.h b/RMQClient/RMQMethods.h index 3b0badb6..cc10711f 100644 --- a/RMQClient/RMQMethods.h +++ b/RMQClient/RMQMethods.h @@ -86,6 +86,15 @@ typedef NS_OPTIONS(NSUInteger, RMQConnectionOpenOptions) { @end @interface RMQConnectionUnblocked : RMQValue +@end +@interface RMQConnectionUpdateSecret : RMQValue +@property (nonnull, copy, nonatomic, readonly) RMQLongstr *secret; +@property (nonnull, copy, nonatomic, readonly) RMQShortstr *reason; +- (nonnull instancetype)initWithSecret:(nonnull RMQLongstr *)secret + reason:(nonnull RMQShortstr *)reason; +@end +@interface RMQConnectionUpdateSecretOk : RMQValue + @end @interface RMQChannelOpen : RMQValue @property (nonnull, copy, nonatomic, readonly) RMQShortstr *reserved1; diff --git a/RMQClient/RMQMethods.m b/RMQClient/RMQMethods.m index 4ae31312..eb515b9f 100644 --- a/RMQClient/RMQMethods.m +++ b/RMQClient/RMQMethods.m @@ -639,6 +639,97 @@ - (NSNumber *)frameTypeID { return @1; } - (BOOL)hasContent { return NO; } +- (instancetype)initWithDecodedFrame:(NSArray *)frame { + self = [super init]; + if (self) { + self.payloadArguments = @[]; + } + return self; +} + +- (NSData *)amqEncoded { + NSMutableData *encoded = [NSMutableData new]; + [encoded appendData:[[RMQShort alloc] init:self.classID.integerValue].amqEncoded]; + [encoded appendData:[[RMQShort alloc] init:self.methodID.integerValue].amqEncoded]; + for (idarg in self.payloadArguments) { + [encoded appendData:arg.amqEncoded]; + } + return encoded; +} + +@end + +@interface RMQConnectionUpdateSecret () +@property (nonnull, copy, nonatomic, readwrite) RMQLongstr *secret; +@property (nonnull, copy, nonatomic, readwrite) RMQShortstr *reason; +@property (nonatomic, readwrite) NSArray *payloadArguments; +@property (nonatomic, readwrite) BOOL hasContent; +@end + +@implementation RMQConnectionUpdateSecret + ++ (NSArray *)propertyClasses { + return @[[RMQLongstr class], + [RMQShortstr class]]; +} +- (NSNumber *)classID { return @10; } +- (NSNumber *)methodID { return @70; } +- (Class)syncResponse { return [RMQConnectionUpdateSecretOk class]; } +- (NSNumber *)frameTypeID { return @1; } +- (BOOL)hasContent { return NO; } + +- (nonnull instancetype)initWithSecret:(nonnull RMQLongstr *)secret + reason:(nonnull RMQShortstr *)reason { + self = [super init]; + if (self) { + self.secret = secret; + self.reason = reason; + self.payloadArguments = @[self.secret, + self.reason]; + } + return self; +} + +- (instancetype)initWithDecodedFrame:(NSArray *)frame { + self = [super init]; + if (self) { + self.secret = ((RMQLongstr *)frame[0]); + self.reason = ((RMQShortstr *)frame[1]); + self.payloadArguments = @[self.secret, + self.reason]; + } + return self; +} + +- (NSData *)amqEncoded { + NSMutableData *encoded = [NSMutableData new]; + [encoded appendData:[[RMQShort alloc] init:self.classID.integerValue].amqEncoded]; + [encoded appendData:[[RMQShort alloc] init:self.methodID.integerValue].amqEncoded]; + for (idarg in self.payloadArguments) { + [encoded appendData:arg.amqEncoded]; + } + return encoded; +} + +@end + +@interface RMQConnectionUpdateSecretOk () +@property (nonatomic, readwrite) NSArray *payloadArguments; +@property (nonatomic, readwrite) BOOL hasContent; +@end + +@implementation RMQConnectionUpdateSecretOk + ++ (NSArray *)propertyClasses { + return @[]; +} +- (NSNumber *)classID { return @10; } +- (NSNumber *)methodID { return @71; } +- (Class)syncResponse { return nil; } +- (NSNumber *)frameTypeID { return @1; } +- (BOOL)hasContent { return NO; } + + - (instancetype)initWithDecodedFrame:(NSArray *)frame { self = [super init]; if (self) { diff --git a/RMQClientTests/ConnectionUpdateSecretTest.swift b/RMQClientTests/ConnectionUpdateSecretTest.swift new file mode 100644 index 00000000..873bac29 --- /dev/null +++ b/RMQClientTests/ConnectionUpdateSecretTest.swift @@ -0,0 +1,26 @@ +// +// ConnectionUpdateSecretTest.swift +// RMQClientTests +// +// Created by Andrew Urban on 22.02.2024. +// Copyright © 2024 VMware. All rights reserved. +// + +import XCTest + +final class ConnectionUpdateSecretTest: XCTestCase { + + func testSendsUpdateSecretMethod() { + let (transport, q, conn, _) = ConnectionWithFakesHelper.connectionAfterHandshake() + + let secret = "someSecret" + let reason = "some test reason" + + conn.updateSecret(secret, reason: reason); + + try? q.step() + + transport.assertClientSentMethod(MethodFixtures.connectionUpdateSecret(secret, reason: reason), channelNumber: 0) + } + +} diff --git a/RMQClientTests/MethodFixtures.swift b/RMQClientTests/MethodFixtures.swift index a8748a59..3f84613e 100644 --- a/RMQClientTests/MethodFixtures.swift +++ b/RMQClientTests/MethodFixtures.swift @@ -214,6 +214,11 @@ class MethodFixtures { static func connectionTuneOk() -> RMQConnectionTuneOk { return RMQConnectionTuneOk(channelMax: RMQShort(65535), frameMax: RMQLong(RMQFrameMax), heartbeat: RMQShort(60)) } + + static func connectionUpdateSecret(_ secret: String, + reason: String) -> RMQConnectionUpdateSecret { + return RMQConnectionUpdateSecret(secret: RMQLongstr(secret), reason: RMQShortstr(reason)) + } static func exchangeBind(_ source: String, destination: String, routingKey: String) -> RMQExchangeBind { return RMQExchangeBind(destination: destination, source: source, routingKey: routingKey) diff --git a/codegen/amqp0-9-1.extended.xml b/codegen/amqp0-9-1.extended.xml index f7d4fd8f..836ed1b7 100644 --- a/codegen/amqp0-9-1.extended.xml +++ b/codegen/amqp0-9-1.extended.xml @@ -4,6 +4,7 @@ WARNING: Modified from the official 0-9-1 specification XML by the addition of: confirm.select and confirm.select-ok, + connection.update-secret and connection.update-secret-ok, exchange.bind and exchange.bind-ok, exchange.unbind and exchange.unbind-ok, basic.nack, @@ -914,6 +915,35 @@ + + + + This method updates the secret used to authenticate this connection. It is used when secrets have an expiration date and need to be renewed, like OAuth 2 tokens. + + + + + + + + The new secret. + + + + + + The reason for the secret update. + + + + + + + This method confirms the updated secret is valid. + + + + diff --git a/docker/Dockerfile b/docker/Dockerfile index 2a488da0..65316fab 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,16 +1,4 @@ -FROM ubuntu:18.04 - -RUN apt-get update -y -RUN apt-get install -y gnupg2 wget -RUN wget -O - "https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc" | apt-key add - - -COPY apt/sources.list.d/bintray.rabbitmq.list /etc/apt/sources.list.d/bintray.rabbitmq.list -COPY apt/preferences.d/erlang /etc/apt/preferences.d/erlang - -RUN apt-get update -y - -RUN apt-get upgrade -y && \ - apt-get install -y rabbitmq-server +FROM rabbitmq:3.12-management COPY docker-entrypoint.sh / COPY certificates/*.pem /etc/rabbitmq/ diff --git a/docker/apt/preferences.d/erlang b/docker/apt/preferences.d/erlang deleted file mode 100644 index 491e5b6c..00000000 --- a/docker/apt/preferences.d/erlang +++ /dev/null @@ -1,3 +0,0 @@ -Package: erlang* -Pin: release o=Bintray -Pin-Priority: 1000 diff --git a/docker/apt/sources.list.d/bintray.rabbitmq.list b/docker/apt/sources.list.d/bintray.rabbitmq.list deleted file mode 100644 index d27fe700..00000000 --- a/docker/apt/sources.list.d/bintray.rabbitmq.list +++ /dev/null @@ -1,2 +0,0 @@ -deb http://dl.bintray.com/rabbitmq-erlang/debian bionic erlang -deb http://dl.bintray.com/rabbitmq/debian bionic main