Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions cloning-instance-with-rest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ Working with the CLI, complete the following steps.
<td><i>target_region</i></td>
<td>The region that the instance of App ID that you want to copy the configuration to is located in. If not provided, it will default to the source region.</td>
</tr>
<tr>
<td><i>attr_name</i></td>
<td><i>(Optional)</i>When migrating users, only migrate users that have a matching attribute name and value pair.</td>
</tr>
<tr>
<td><i>attr_value</i></td>
<td><i>(Optional)</i>When migrating users, only migrate users that have a matching attribute name and value pair.</td>
</tr>
<tr>
<td><i>clientid</i></td>
<td><i>(Optional)</i>When migrating user profiles, the client ID of the instance of App ID that you want to copy the user profiles to.</td>
</tr>
<tr>
<td><i>secret</i></td>
<td><i>(Optional)</i> When migrating user profiles, the client secret of the instance of App ID that you want to copy the user profiles to.</td>
</tr>
</table>

>Tip: To see the REST messages, append the `-v` flag to the command.
Expand All @@ -100,6 +116,21 @@ Example:
appidc xxxxxxx yyyyyyy --apikey KkKkKkKkkKkKkKkKk --region eu-gb
```

As another example, you can choose to migrate only specific users by providing a user profile attribute name and value. Only users with a matching attribute name and value will be migrated.

Example:
```
appidc xxxxxxx yyyyyyy --apikey KkKkKkKkkKkKkKkKk --region eu-gb -attr_name user-status -attr_value active
```

If you wish to also migrate user profiles, you must also specify a valid client ID and secret of a credential on the target App ID instance.
Note that users that are migrated will automatically be requested to reset their password.
Example:
```
appidc xxxxxxx yyyyyyy --apikey KkKkKkKkkKkKkKkKk --region eu-gb -clientid ccccccccc -secret ssssssssssssss
```


And that's it!

To go even further with App ID and access management, check out [our documentation](https://console.bluemix.net/docs/services/appid/api-reference.html)!
185 changes: 170 additions & 15 deletions cloning-instance-with-rest/appid_instance_copy/copy_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,69 @@
import requests
import json
import argparse
import base64
import urllib
import random
import sys



def get_from_api(path, token):
def get_from_api(path, token, isConfig=True, tenantId=None):
if tenantId is None:
tenantId = src_tenantId

headers = {'Authorization': token, 'Accept': 'application/json'}
url = src_management_url + src_tenantId + "/config/" +path
configPath = "/config" if isConfig else ""
url = src_management_url + tenantId + configPath + "/" +path

return requests.get(
url,
headers=headers);
headers=headers);

def put_to_api(path, content, token):
def put_to_api(path, content, token, isConfig=True):
headers = {'Authorization': token, 'Accept': 'application/json', 'Content-Type': 'application/json'}
url = tgt_management_url + trgt_tenantId + "/config/" + path
configPath = "/config" if isConfig else ""
url = tgt_management_url + trgt_tenantId + configPath + "/" + path
return requests.put(
url,
data=content,
headers=headers);

def post_to_api(path, content, token, isConfig=True, isJson=True):
contentType = "application/json" if isJson else "application/x-www-form-urlencoded"
headers = {'Authorization': token, 'Accept': 'application/json', 'Content-Type': contentType}
configPath = "/config" if isConfig else ""
url = tgt_management_url + trgt_tenantId + configPath + "/" + path
return requests.post(
url,
data=content,
headers=headers);

def get_iam_token():
headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'}
data = 'grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=' + apiKey;

r = requests.post(iam_url + ".bluemix.net/oidc/token", data=data, headers=headers);
return 'Bearer ' + json.loads(r.text)['access_token'];

def get_user_token(loginId, password):

basicAuthHeader = 'Basic ' + str(base64.b64encode(bytes(clientId + ":" + clientSecret)))
headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json', 'Authorization': basicAuthHeader}
data = 'grant_type=password&username=' + urllib.quote(loginId) + '&password=' + password;

r = requests.post(tgt_oauth_url, data=data, headers=headers);
accessToken = json.loads(r.text).get('access_token', None)
if accessToken is None:
print("Couldn't get user access token - " + str(r.content))
return

return 'Bearer ' + accessToken

def copy(path, token):
r = get_from_api(path, token)
def copy(path, token, isConfig=True):
r = get_from_api(path, token, isConfig)
debug(r.status_code)
debug(r.content)
if 200 <= r.status_code < 300:
print("success! got " + path + " from source")
print("Success! got " + path + " from source")
else:
print("Failed to get " + path + " from source")
jcontent = r.content
Expand All @@ -41,7 +73,7 @@ def copy(path, token):
debug(r.status_code)
debug(r.text)
if 200 <= r.status_code < 300:
print("success! put to " + path + " at target")
print("Success! put to " + path + " at target")
else:
print("Failed to put to " + path + " at target")

Expand All @@ -54,10 +86,108 @@ def copyTemplates(token):
def copyActions(token):
copy("cloud_directory/action_url/" + "on_user_verified", token)
copy("cloud_directory/action_url/" + "on_reset_password", token)

def copyCloudUsers(token):
path = "cloud_directory/Users"
users = get_from_api(path, token, False)
data = json.loads(users.content)
resources = data['Resources']
for user in resources:
debug("Processing user " + user['displayName'])
loginId = user['emails'][0]['value']
userProfile = get_user_profile(loginId, token)
if is_user_attribute_filter():
userProfileValue = None
if userProfile and userProfile['attributes']:
userProfileValue = userProfile['attributes'].get(attrName, None)

if not userProfileValue or not userProfileValue == attrValue:
print("Skipping user with incorrect " + attrName + " : is " + str(userProfileValue) + " expected " + attrValue)
continue

user['password'] = str(random.randint(1, sys.maxsize))
r = post_to_api(path, json.dumps(user), token, False)
debug(r.status_code)
debug(r.text)
if 200 <= r.status_code < 300:
print("Success! put to " + path + " at target")

if is_copy_user_profile():
copy_user_profile(userProfile, loginId, user['password'], token)

request_password_reset(loginId, token)

else:
print("Failed to put to " + path + " at target: " + str(r.status_code))

def get_user_profile_id(loginId, token, tenantId=None):

user = get_from_api("users?email="+urllib.quote(loginId), token, False, tenantId)
userData = json.loads(user.content)['users']

if not userData:
return

return userData[0]['id']

def get_user_profile(loginId, token):
userProfileId = get_user_profile_id(loginId, token)

if (userProfileId is not None):
profile = get_from_api("users/" + userProfileId + "/profile", token, False)
return json.loads(profile.content)

return

def is_user_attribute_filter():
return (attrName is not None) and (attrValue is not None)

def is_copy_user_profile():
return (clientId is not None) and (clientSecret is not None)

def copy_user_profile(userProfile, loginId, password, token):
if not userProfile or not userProfile['attributes']:
return

perform_user_login(loginId, password)

#must retrieve the target user profile id
targetProfileId = get_user_profile_id(loginId, token, trgt_tenantId)
if targetProfileId is None:
print("Couldn't find target profile for user " + loginId)
return

path = "users/" + targetProfileId + "/profile"
r = put_to_api(path, json.dumps(userProfile), token, False)
debug(r.status_code)
debug(r.text)
if 200 <= r.status_code < 300:
print("Success! put to " + path + " at target")
else:
print("Failed to put to " + path + " at target: " + str(r.status_code))

#User login is currently required to initialize a new user's profile.
#This must be conducted prior to adding profile attributes.
def perform_user_login(loginId, password):
userToken = get_user_token(loginId, password)

def request_password_reset(loginId, token):
path = "cloud_directory/forgot_password"
params = "email=" + urllib.quote(loginId)
r = post_to_api(path, params, token, False, False)
debug(r.status_code)
debug(r.text)

if 200 <= r.status_code < 300:
debug("Success! post to " + path + " at target")
else:
print("Failed to post to " + path + " at target: " + str(r.status_code))

def debug(str):
if verbose:
print(str)



def main():
parser = argparse.ArgumentParser(description='Copy configuration from one App ID instance to another')
Expand All @@ -78,6 +208,16 @@ def main():

parser.add_argument('-v', '--verbose', action='store_true',
help='Run with verbose mode. REST messages content will be displayed')

parser.add_argument('-a', '--attr_name', type=str,
help='User profile attribute name for user selection')
parser.add_argument('-l', '--attr_value', type=str,
help='User profile attribute value for user selection')

parser.add_argument('-c', '--clientid', type=str,
help='Client ID for user profile migration')
parser.add_argument('-s', '--secret', type=str,
help='Client secret for user profile migration')

args = parser.parse_args()

Expand All @@ -88,14 +228,23 @@ def main():
global iam_url
global src_management_url
global tgt_management_url
global tgt_oauth_url
global verbose
global attrName
global attrValue
global clientId
global clientSecret

src_tenantId = args.source
trgt_tenantId = args.target
apiKey = args.apikey
region = args.region
tgt_region = args.target_region
verbose = args.verbose
attrName = args.attr_name
attrValue = args.attr_value
clientId = args.clientid
clientSecret = args.secret

if region == "us-south":
region = 'ng'
Expand All @@ -109,16 +258,21 @@ def main():
iam_url = "https://iam." + region
src_management_url = "https://appid-management." + region + ".bluemix.net/management/v4/"
tgt_management_url = "https://appid-management." + tgt_region + ".bluemix.net/management/v4/"
tgt_oauth_url = "https://appid-oauth." + tgt_region + ".bluemix.net/oauth/v3/" + trgt_tenantId + "/token"

debug("source:" + src_management_url)
debug("target:" + tgt_management_url)
debug("target oauth:" + tgt_oauth_url)
debug("attrName:" + str(attrName))
debug("attrValue:" + str(attrValue))

token = get_iam_token()

copy("idps/facebook", token)
copy("idps/google", token)
#copy("idps/facebook", token)
#copy("idps/google", token)
copy("idps/cloud_directory", token)
# copy("idps/saml", token)
#

copy("tokens", token)
# copy("redirect_uris", token)
copy("users_profile", token)
Expand All @@ -128,7 +282,8 @@ def main():

copyTemplates(token)
#copyActions(token)


copyCloudUsers(token)


if __name__ == "__main__":
Expand Down
1 change: 1 addition & 0 deletions openidauth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.gradle/
1 change: 1 addition & 0 deletions openidauth/OpenidAuth-ear/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
26 changes: 26 additions & 0 deletions openidauth/OpenidAuth-ear/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apply plugin: 'ear'
apply plugin: 'java'
apply plugin: 'maven-publish'

dependencies {
deploy project(path: ':OpenidAuth-war', configuration: 'archives')
}

ear {
baseName = 'OpenidAuth'
deploymentDescriptor {
applicationName = "OpenidAuth"
initializeInOrder = true
description = "An application for custom integration with OAuth2 Identity Providers."
}
}

publishing {
publications {
mavenJava(MavenPublication) {
groupId 'com.ibm.openid'
artifactId 'OpenidAuth'
artifact ear
}
}
}
5 changes: 5 additions & 0 deletions openidauth/OpenidAuth-war/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/
build/
.settings/
.classpath
.project
22 changes: 22 additions & 0 deletions openidauth/OpenidAuth-war/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apply plugin: 'war'
apply plugin: 'eclipse'

war {
archiveName = 'OpenidAuth.war'
}

dependencies {
compile('javax.servlet:javax.servlet-api:3.1.0')
compile('javax.ws.rs:javax.ws.rs-api:2.0.1')
compile('net.sf.ehcache:ehcache-core:2.6.11')
compile('org.glassfish.hk2:hk2-api:2.5.0-b05')
compile('com.fasterxml.jackson.core:jackson-annotations:2.9.5')
compile('com.fasterxml.jackson.core:jackson-core:2.9.5')
compile('com.fasterxml.jackson.core:jackson-databind:2.9.5')
compile('com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:2.9.5')
compile('com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.9.5')
compile('org.glassfish.jersey.core:jersey-client:2.6')
compile('org.glassfish.jersey.core:jersey-common:2.6')
compile('org.slf4j:slf4j-api:1.7.25')
compile('org.slf4j:slf4j-jdk14:1.7.25')
}
Loading