Skip to content

Commit

Permalink
Add hybrid app support to FBSDK
Browse files Browse the repository at this point in the history
Summary:
Hybrid apps, which use a combination of read and write permissions (consumer and business, really) are not properly supported by the current SDK.

As shown in the CHANGELOG, these changes add:

- `FBSDKLoginManager -logInWithPermissions:fromViewController:handler:`
- `FBSDKLoginButton permissions`
- `FBSDKDeviceLoginButton permissions`
- `FBSDKDeviceLoginViewController permissions`

And deprecate:

- `FBSDKLoginManager -logInWithReadPermissions:fromViewController:handler:`
- `FBSDKLoginManager -logInWithWritePermissions:fromViewController:handler:`
- `FBSDKLoginButton readPermissions`
- `FBSDKLoginButton writePermissions`
- `FBSDKDeviceLoginButton readPermissions`
- `FBSDKDeviceLoginButton writePermissions`
- `FBSDKDeviceLoginViewController readPermissions`
- `FBSDKDeviceLoginViewController writePermissions`

Reviewed By: codytwinton

Differential Revision: D14849648

fbshipit-source-id: 39c086b3f291ee22bb3d00f3e907ab5ba8835e02
  • Loading branch information
robtimp authored and facebook-github-bot committed Apr 10, 2019
1 parent 17dd038 commit 6c4ac12
Show file tree
Hide file tree
Showing 28 changed files with 251 additions and 265 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- `NS_ASSUME_NONNULL_BEGIN`, `NS_ASSUME_NONNULL_END`, and other nullability annotations
- Generics for Arrays, Sets, and Dictionaries
- `NS_SWIFT_NAME` to remove the `FBSDK` prefix where necessary (left `FB` prefix for UI elements)
- `FBSDKLoginManager -logInWithPermissions:fromViewController:handler:`
- `FBSDKLoginButton permissions`
- `FBSDKDeviceLoginButton permissions`
- `FBSDKDeviceLoginViewController permissions`

### Changed

Expand All @@ -35,6 +39,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Collections/Dictionaries became non null when at all possible
- Class creation methods become Swift inits

### Deprecated

- `FBSDKLoginManager -logInWithReadPermissions:fromViewController:handler:`
- `FBSDKLoginManager -logInWithWritePermissions:fromViewController:handler:`
- `FBSDKLoginButton readPermissions`
- `FBSDKLoginButton writePermissions`
- `FBSDKDeviceLoginButton readPermissions`
- `FBSDKDeviceLoginButton writePermissions`
- `FBSDKDeviceLoginViewController readPermissions`
- `FBSDKDeviceLoginViewController writePermissions`

### Removed

- Deprecated methods
Expand Down
2 changes: 1 addition & 1 deletion Configurations/FacebookSDK-Project.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

// Architectures
ARCHS = i386 armv7 x86_64 arm64
IPHONEOS_DEPLOYMENT_TARGET = 7.0
IPHONEOS_DEPLOYMENT_TARGET = 8.0
SDKROOT = iphoneos

// Build Options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,41 +44,43 @@ + (NSString *)findParameterOfPath:(NSArray *)path
@implementation FBSDKEventBindingTests

- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
eventBindingManager = [[FBSDKEventBindingManager alloc]
initWithJSON:[FBSDKSampleEventBinding getSampleDictionary]];
window = [[UIWindow alloc] init];
UIViewController *vc = [[UIViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];

UITabBarController *tab = [[UITabBarController alloc] init];
tab.viewControllers = @[nav];
window.rootViewController = tab;

UIStackView *firstStackView = [[UIStackView alloc] init];
[vc.view addSubview:firstStackView];
UIStackView *secondStackView = [[UIStackView alloc] init];
[firstStackView addSubview:secondStackView];

btnBuy = [UIButton buttonWithType:UIButtonTypeCustom];
[btnBuy setTitle:@"Buy" forState:UIControlStateNormal];
[firstStackView addSubview:btnBuy];

UILabel *lblPrice = [[UILabel alloc] init];
lblPrice.text = @"$2.0";
[firstStackView addSubview:lblPrice];

btnConfirm = [UIButton buttonWithType:UIButtonTypeCustom];
[btnConfirm setTitle:@"Confirm" forState:UIControlStateNormal];
[firstStackView addSubview:btnConfirm];

lblPrice = [[UILabel alloc] init];
lblPrice.text = @"$3.0";
[secondStackView addSubview:lblPrice];

stepper = [[UIStepper alloc] init];
[secondStackView addSubview:stepper];
[super setUp];

if (@available(iOS 9.0, *)) {
eventBindingManager = [[FBSDKEventBindingManager alloc]
initWithJSON:[FBSDKSampleEventBinding getSampleDictionary]];
window = [[UIWindow alloc] init];
UIViewController *vc = [[UIViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];

UITabBarController *tab = [[UITabBarController alloc] init];
tab.viewControllers = @[nav];
window.rootViewController = tab;

UIStackView *firstStackView = [[UIStackView alloc] init];
[vc.view addSubview:firstStackView];
UIStackView *secondStackView = [[UIStackView alloc] init];
[firstStackView addSubview:secondStackView];

btnBuy = [UIButton buttonWithType:UIButtonTypeCustom];
[btnBuy setTitle:@"Buy" forState:UIControlStateNormal];
[firstStackView addSubview:btnBuy];

UILabel *lblPrice = [[UILabel alloc] init];
lblPrice.text = @"$2.0";
[firstStackView addSubview:lblPrice];

btnConfirm = [UIButton buttonWithType:UIButtonTypeCustom];
[btnConfirm setTitle:@"Confirm" forState:UIControlStateNormal];
[firstStackView addSubview:btnConfirm];

lblPrice = [[UILabel alloc] init];
lblPrice.text = @"$3.0";
[secondStackView addSubview:lblPrice];

stepper = [[UIStepper alloc] init];
[secondStackView addSubview:stepper];
}
}

- (void)tearDown {
Expand Down
22 changes: 12 additions & 10 deletions FBSDKLoginKit/FBSDKLoginKit/FBSDKLoginButton.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,24 @@ NS_SWIFT_NAME(FBLoginButton)
Gets or sets the login behavior to use
*/
@property (assign, nonatomic) FBSDKLoginBehavior loginBehavior;
/**
The publish permissions to request.

Use `defaultAudience` to specify the default audience to publish to.
/*!
@abstract The permissions to request.
@discussion To provide the best experience, you should minimize the number of permissions you request, and only ask for them when needed.
For example, do not ask for "user_location" until you the information is actually used by the app.
Note this is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
See [the permissions guide]( https://developers.facebook.com/docs/facebook-login/permissions/ ) for more details.
*/
@property (copy, nonatomic) NSArray<NSString *> *publishPermissions;
/**
The read permissions to request.
@property (copy, nonatomic) NSArray<NSString *> *permissions;

@property (copy, nonatomic) NSArray<NSString *> *publishPermissions
DEPRECATED_MSG_ATTRIBUTE("Use permissions instead.");

Note, that if read permissions are specified, then publish permissions should not be specified. This is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
*/
@property (copy, nonatomic) NSArray<NSString *> *readPermissions;
@property (copy, nonatomic) NSArray<NSString *> *readPermissions
DEPRECATED_MSG_ATTRIBUTE("Use permissions instead.");
/**
Gets or sets the desired tooltip behavior.
*/
Expand Down
31 changes: 22 additions & 9 deletions FBSDKLoginKit/FBSDKLoginKit/FBSDKLoginButton.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ - (void)setLoginBehavior:(FBSDKLoginBehavior)loginBehavior
_loginManager.loginBehavior = loginBehavior;
}

- (void)setReadPermissions:(NSArray<NSString *> *)readPermissions
{
_readPermissions = readPermissions;

NSMutableSet<NSString *> *newPermissions = [NSMutableSet setWithArray:_publishPermissions];
[newPermissions addObjectsFromArray:readPermissions];
_permissions = [newPermissions allObjects];
}

- (void)setPublishPermissions:(NSArray<NSString *> *)publishPermissions
{
_publishPermissions = publishPermissions;

NSMutableSet<NSString *> *newPermissions = [NSMutableSet setWithArray:_readPermissions];
[newPermissions addObjectsFromArray:publishPermissions];
_permissions = [newPermissions allObjects];
}

- (UIFont *)defaultFont
{
return [UIFont systemFontOfSize:13];
Expand Down Expand Up @@ -263,15 +281,10 @@ - (void)_buttonPressed:(id)sender
}
};

if (self.publishPermissions.count > 0) {
[_loginManager logInWithPublishPermissions:self.publishPermissions
fromViewController:[FBSDKInternalUtility viewControllerForView:self]
handler:handler];
} else {
[_loginManager logInWithReadPermissions:self.readPermissions
fromViewController:[FBSDKInternalUtility viewControllerForView:self]
handler:handler];
}
[_loginManager logInWithPermissions:self.permissions
fromViewController:[FBSDKInternalUtility viewControllerForView:self]
handler:handler];

}
}

Expand Down
36 changes: 12 additions & 24 deletions FBSDKLoginKit/FBSDKLoginKit/FBSDKLoginManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,52 +122,40 @@ NS_SWIFT_NAME(LoginManager)
@property (assign, nonatomic) FBSDKLoginBehavior loginBehavior;

/**
Logs the user in or authorizes additional permissions.
Logs the user in or authorizes additional permissions.
@param permissions the optional array of permissions. Note this is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
an NSArray for the convenience of literal syntax.
@param fromViewController the view controller to present from. If nil, the topmost view controller will be
automatically determined as best as possible.
automatically determined as best as possible.
@param handler the callback.
Use this method when asking for read permissions. You should only ask for permissions when they
are needed and explain the value to the user. You can inspect the result.declinedPermissions to also
provide more information to the user if they decline permissions.
are needed and explain the value to the user. You can inspect the result.declinedPermissions to also
provide more information to the user if they decline permissions.
This method will present UI the user. You typically should check if `[FBSDKAccessToken currentAccessToken]`
already contains the permissions you need before asking to reduce unnecessary app switching. For example,
you could make that check at viewDidLoad.
You can only do one login call at a time. Calling a login method before the completion handler is called
on a previous login will return an error.
*/
- (void)logInWithPermissions:(NSArray<NSString *> *)permissions
fromViewController:(nullable UIViewController *)fromViewController
handler:(nullable FBSDKLoginManagerLoginResultBlock)handler
NS_SWIFT_NAME(logIn(permissions:from:handler:));

- (void)logInWithReadPermissions:(NSArray<NSString *> *)permissions
fromViewController:(nullable UIViewController *)fromViewController
handler:(nullable FBSDKLoginManagerLoginResultBlock)handler
__attribute__((deprecated("Use -logInWithPermissions:fromViewController:handler: instead.", "-logInWithPermissions:fromViewController:handler:")))
NS_SWIFT_NAME(logIn(readPermissions:from:handler:));

/**
Logs the user in or authorizes additional permissions.
@param permissions the optional array of permissions. Note this is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
@param fromViewController the view controller to present from. If nil, the topmost view controller will be
automatically determined as best as possible.
@param handler the callback.
Use this method when asking for publish permissions. You should only ask for permissions when they
are needed and explain the value to the user. You can inspect the result.declinedPermissions to also
provide more information to the user if they decline permissions.
This method will present UI the user. You typically should check if `[FBSDKAccessToken currentAccessToken]`
already contains the permissions you need before asking to reduce unnecessary app switching. For example,
you could make that check at viewDidLoad.
You can only do one login call at a time. Calling a login method before the completion handler is called
on a previous login will return an error.
*/
- (void)logInWithPublishPermissions:(NSArray<NSString *> *)permissions
fromViewController:(nullable UIViewController *)fromViewController
handler:(nullable FBSDKLoginManagerLoginResultBlock)handler
__attribute__((deprecated("Use -logInWithPermissions:fromViewController:handler: instead.", "-logInWithPermissions:fromViewController:handler:")))
NS_SWIFT_NAME(logIn(publishPermissions:from:handler:));


/**
Requests user's permission to reathorize application's data access, after it has expired due to inactivity.
@param fromViewController the view controller to present from. If nil, the topmost view controller will be
Expand Down
36 changes: 13 additions & 23 deletions FBSDKLoginKit/FBSDKLoginKit/FBSDKLoginManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -76,40 +76,30 @@ - (instancetype)init
return self;
}

- (void)logInWithReadPermissions:(NSArray *)permissions
fromViewController:(UIViewController *)fromViewController
handler:(FBSDKLoginManagerLoginResultBlock)handler
- (void)logInWithPermissions:(NSArray<NSString *> *)permissions
fromViewController:(UIViewController *)fromViewController
handler:(FBSDKLoginManagerLoginResultBlock)handler
{
if (![self validateLoginStartState]) {
return;
}
[self assertPermissions:permissions];
NSSet *permissionSet = [NSSet setWithArray:permissions];
if (![FBSDKInternalUtility areAllPermissionsReadPermissions:permissionSet]) {
[self raiseLoginException:[NSException exceptionWithName:NSInvalidArgumentException
reason:@"Publish or manage permissions are not permitted to be requested with read permissions."
userInfo:nil]];
}
self.fromViewController = fromViewController;
NSSet<NSString *> *permissionSet = [NSSet setWithArray:permissions];
[self logInWithPermissions:permissionSet handler:handler];
}

- (void)logInWithPublishPermissions:(NSArray *)permissions
- (void)logInWithReadPermissions:(NSArray<NSString *> *)permissions
fromViewController:(UIViewController *)fromViewController
handler:(FBSDKLoginManagerLoginResultBlock)handler
{
[self logInWithPermissions:permissions fromViewController:fromViewController handler:handler];
}

- (void)logInWithPublishPermissions:(NSArray<NSString *> *)permissions
fromViewController:(UIViewController *)fromViewController
handler:(FBSDKLoginManagerLoginResultBlock)handler
{
if (![self validateLoginStartState]) {
return;
}
[self assertPermissions:permissions];
NSSet *permissionSet = [NSSet setWithArray:permissions];
if (![FBSDKInternalUtility areAllPermissionsPublishPermissions:permissionSet]) {
[self raiseLoginException:[NSException exceptionWithName:NSInvalidArgumentException
reason:@"Read permissions are not permitted to be requested with publish or manage permissions."
userInfo:nil]];
}
self.fromViewController = fromViewController;
[self logInWithPermissions:permissionSet handler:handler];
[self logInWithPermissions:permissions fromViewController:fromViewController handler:handler];
}

- (void)reauthorizeDataAccess:(UIViewController *)fromViewController handler:(FBSDKLoginManagerLoginResultBlock)handler
Expand Down
30 changes: 3 additions & 27 deletions FBSDKLoginKit/FBSDKLoginKitTests/FBSDKLoginManagerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -223,30 +223,6 @@ - (void)testOpenURLReauthNoPermissionsIsNotCancelled
[target verify];
}

- (void)testInvalidPermissions
{
FBSDKLoginManager *target = [self loginManagerExpectingChallenge];
NSArray *publishPermissions = @[@"publish_actions", @"manage_notifications"];
NSArray *readPermissions = @[@"user_birthday", @"user_hometown"];
XCTAssertThrowsSpecificNamed([target logInWithPublishPermissions:@[[publishPermissions componentsJoinedByString:@","]]
fromViewController:nil
handler:NULL],
NSException,
NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([target logInWithPublishPermissions:readPermissions
fromViewController:nil
handler:NULL],
NSException, NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([target logInWithReadPermissions:@[[readPermissions componentsJoinedByString:@","]]
fromViewController:nil
handler:NULL],
NSException,
NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([target logInWithReadPermissions:publishPermissions
fromViewController:nil
handler:NULL], NSException, NSInvalidArgumentException);
}

- (void)testOpenURLWithBadChallenge
{
XCTestExpectation *expectation = [self expectationWithDescription:@"completed auth"];
Expand Down Expand Up @@ -305,7 +281,7 @@ - (void)testLoginManagerRetainsItselfForLoginMethod

XCTestExpectation *expectation = [self expectationWithDescription:@"completed auth"];
FBSDKLoginManager *manager = [FBSDKLoginManager new];
[manager logInWithReadPermissions:@[@"public_profile"] fromViewController:nil handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
[manager logInWithPermissions:@[@"public_profile"] fromViewController:nil handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
[expectation fulfill];
}];
// This makes sure that FBSDKLoginManager is retaining itself for the duration of the call
Expand All @@ -329,12 +305,12 @@ - (void)testCallingLoginWhileAnotherLoginHasNotFinishedNoOps
[[[(id)manager stub] andDo:^(NSInvocation *invocation) {
loginCount++;
}] logInWithBehavior:FBSDKLoginBehaviorBrowser];
[manager logInWithReadPermissions:@[@"public_profile"] fromViewController:nil handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
[manager logInWithPermissions:@[@"public_profile"] fromViewController:nil handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
// This will never be called
XCTFail(@"Should not be called");
}];

[manager logInWithReadPermissions:@[@"public_profile"] fromViewController:nil handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
[manager logInWithPermissions:@[@"public_profile"] fromViewController:nil handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
// This will never be called
XCTFail(@"Should not be called");
}];
Expand Down
Loading

0 comments on commit 6c4ac12

Please sign in to comment.